• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
3#   Copyright 2021 - 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
17import collections
18import logging
19import math
20import os
21import re
22import shutil
23import socket
24import time
25from builtins import open
26from builtins import str
27from datetime import datetime
28
29from acts import context
30from acts import logger as acts_logger
31from acts import tracelogger
32from acts import utils
33from acts.controllers import adb
34from acts.controllers.adb_lib.error import AdbError
35from acts.controllers import fastboot
36from acts.controllers.android_lib import errors
37from acts.controllers.android_lib import events as android_events
38from acts.controllers.android_lib import logcat
39from acts.controllers.android_lib import services
40from acts.controllers.sl4a_lib import sl4a_manager
41from acts.controllers.utils_lib.ssh import connection
42from acts.controllers.utils_lib.ssh import settings
43from acts.event import event_bus
44from acts.libs.proc import job
45from acts.metrics.loggers.usage_metadata_logger import record_api_usage
46
47MOBLY_CONTROLLER_CONFIG_NAME = "AndroidDevice"
48ACTS_CONTROLLER_REFERENCE_NAME = "android_devices"
49
50ANDROID_DEVICE_PICK_ALL_TOKEN = "*"
51# Key name for SL4A extra params in config file
52ANDROID_DEVICE_SL4A_CLIENT_PORT_KEY = "sl4a_client_port"
53ANDROID_DEVICE_SL4A_FORWARDED_PORT_KEY = "sl4a_forwarded_port"
54ANDROID_DEVICE_SL4A_SERVER_PORT_KEY = "sl4a_server_port"
55# Key name for adb logcat extra params in config file.
56ANDROID_DEVICE_ADB_LOGCAT_PARAM_KEY = "adb_logcat_param"
57ANDROID_DEVICE_EMPTY_CONFIG_MSG = "Configuration is empty, abort!"
58ANDROID_DEVICE_NOT_LIST_CONFIG_MSG = "Configuration should be a list, abort!"
59CRASH_REPORT_PATHS = ("/data/tombstones/", "/data/vendor/ramdump/",
60                      "/data/ramdump/", "/data/vendor/ssrdump",
61                      "/data/vendor/ramdump/bluetooth", "/data/vendor/log/cbd")
62CRASH_REPORT_SKIPS = ("RAMDUMP_RESERVED", "RAMDUMP_STATUS", "RAMDUMP_OUTPUT",
63                      "bluetooth")
64ALWAYS_ON_LOG_PATH = "/data/vendor/radio/logs/always-on"
65DEFAULT_QXDM_LOG_PATH = "/data/vendor/radio/diag_logs"
66DEFAULT_SDM_LOG_PATH = "/data/vendor/slog/"
67DEFAULT_SCREENSHOT_PATH = "/sdcard/Pictures/screencap"
68BUG_REPORT_TIMEOUT = 1800
69PULL_TIMEOUT = 300
70PORT_RETRY_COUNT = 3
71ADB_ROOT_RETRY_COUNT = 2
72ADB_ROOT_RETRY_INTERVAL = 10
73IPERF_TIMEOUT = 60
74SL4A_APK_NAME = "com.googlecode.android_scripting"
75WAIT_FOR_DEVICE_TIMEOUT = 180
76ENCRYPTION_WINDOW = "CryptKeeper"
77DEFAULT_DEVICE_PASSWORD = "1111"
78RELEASE_ID_REGEXES = [re.compile(r'\w+\.\d+\.\d+'), re.compile(r'N\w+')]
79
80
81def create(configs):
82    """Creates AndroidDevice controller objects.
83
84    Args:
85        configs: A list of dicts, each representing a configuration for an
86                 Android device.
87
88    Returns:
89        A list of AndroidDevice objects.
90    """
91    if not configs:
92        raise errors.AndroidDeviceConfigError(ANDROID_DEVICE_EMPTY_CONFIG_MSG)
93    elif configs == ANDROID_DEVICE_PICK_ALL_TOKEN:
94        ads = get_all_instances()
95    elif not isinstance(configs, list):
96        raise errors.AndroidDeviceConfigError(
97            ANDROID_DEVICE_NOT_LIST_CONFIG_MSG)
98    elif isinstance(configs[0], str):
99        # Configs is a list of serials.
100        ads = get_instances(configs)
101    else:
102        # Configs is a list of dicts.
103        ads = get_instances_with_configs(configs)
104
105    ads[0].log.info('The primary device under test is "%s".' % ads[0].serial)
106
107    for ad in ads:
108        if not ad.is_connected():
109            raise errors.AndroidDeviceError(
110                ("Android device %s is specified in config"
111                 " but is not attached.") % ad.serial,
112                serial=ad.serial)
113    _start_services_on_ads(ads)
114    for ad in ads:
115        if ad.droid:
116            utils.set_location_service(ad, False)
117            utils.sync_device_time(ad)
118    return ads
119
120
121def destroy(ads):
122    """Cleans up AndroidDevice objects.
123
124    Args:
125        ads: A list of AndroidDevice objects.
126    """
127    for ad in ads:
128        try:
129            ad.clean_up()
130        except:
131            ad.log.exception("Failed to clean up properly.")
132
133
134def get_info(ads):
135    """Get information on a list of AndroidDevice objects.
136
137    Args:
138        ads: A list of AndroidDevice objects.
139
140    Returns:
141        A list of dict, each representing info for an AndroidDevice objects.
142    """
143    device_info = []
144    for ad in ads:
145        info = {"serial": ad.serial, "model": ad.model}
146        info.update(ad.build_info)
147        device_info.append(info)
148    return device_info
149
150
151def _start_services_on_ads(ads):
152    """Starts long running services on multiple AndroidDevice objects.
153
154    If any one AndroidDevice object fails to start services, cleans up all
155    existing AndroidDevice objects and their services.
156
157    Args:
158        ads: A list of AndroidDevice objects whose services to start.
159    """
160    running_ads = []
161    for ad in ads:
162        running_ads.append(ad)
163        try:
164            ad.start_services()
165        except:
166            ad.log.exception('Failed to start some services, abort!')
167            destroy(running_ads)
168            raise
169
170
171def _parse_device_list(device_list_str, key):
172    """Parses a byte string representing a list of devices. The string is
173    generated by calling either adb or fastboot.
174
175    Args:
176        device_list_str: Output of adb or fastboot.
177        key: The token that signifies a device in device_list_str.
178
179    Returns:
180        A list of android device serial numbers.
181    """
182    return re.findall(r"(\S+)\t%s" % key, device_list_str)
183
184
185def list_adb_devices():
186    """List all android devices connected to the computer that are detected by
187    adb.
188
189    Returns:
190        A list of android device serials. Empty if there's none.
191    """
192    out = adb.AdbProxy().devices()
193    return _parse_device_list(out, "device")
194
195
196def list_fastboot_devices():
197    """List all android devices connected to the computer that are in in
198    fastboot mode. These are detected by fastboot.
199
200    Returns:
201        A list of android device serials. Empty if there's none.
202    """
203    out = fastboot.FastbootProxy().devices()
204    return _parse_device_list(out, "fastboot")
205
206
207def get_instances(serials):
208    """Create AndroidDevice instances from a list of serials.
209
210    Args:
211        serials: A list of android device serials.
212
213    Returns:
214        A list of AndroidDevice objects.
215    """
216    results = []
217    for s in serials:
218        results.append(AndroidDevice(s))
219    return results
220
221
222def get_instances_with_configs(configs):
223    """Create AndroidDevice instances from a list of json configs.
224
225    Each config should have the required key-value pair "serial".
226
227    Args:
228        configs: A list of dicts each representing the configuration of one
229            android device.
230
231    Returns:
232        A list of AndroidDevice objects.
233    """
234    results = []
235    for c in configs:
236        try:
237            serial = c.pop('serial')
238        except KeyError:
239            raise errors.AndroidDeviceConfigError(
240                "Required value 'serial' is missing in AndroidDevice config %s."
241                % c)
242        client_port = 0
243        if ANDROID_DEVICE_SL4A_CLIENT_PORT_KEY in c:
244            try:
245                client_port = int(c.pop(ANDROID_DEVICE_SL4A_CLIENT_PORT_KEY))
246            except ValueError:
247                raise errors.AndroidDeviceConfigError(
248                    "'%s' is not a valid number for config %s" %
249                    (ANDROID_DEVICE_SL4A_CLIENT_PORT_KEY, c))
250        server_port = None
251        if ANDROID_DEVICE_SL4A_SERVER_PORT_KEY in c:
252            try:
253                server_port = int(c.pop(ANDROID_DEVICE_SL4A_SERVER_PORT_KEY))
254            except ValueError:
255                raise errors.AndroidDeviceConfigError(
256                    "'%s' is not a valid number for config %s" %
257                    (ANDROID_DEVICE_SL4A_SERVER_PORT_KEY, c))
258        forwarded_port = 0
259        if ANDROID_DEVICE_SL4A_FORWARDED_PORT_KEY in c:
260            try:
261                forwarded_port = int(
262                    c.pop(ANDROID_DEVICE_SL4A_FORWARDED_PORT_KEY))
263            except ValueError:
264                raise errors.AndroidDeviceConfigError(
265                    "'%s' is not a valid number for config %s" %
266                    (ANDROID_DEVICE_SL4A_FORWARDED_PORT_KEY, c))
267        ssh_config = c.pop('ssh_config', None)
268        ssh_connection = None
269        if ssh_config is not None:
270            ssh_settings = settings.from_config(ssh_config)
271            ssh_connection = connection.SshConnection(ssh_settings)
272        ad = AndroidDevice(serial,
273                           ssh_connection=ssh_connection,
274                           client_port=client_port,
275                           forwarded_port=forwarded_port,
276                           server_port=server_port)
277        ad.load_config(c)
278        results.append(ad)
279    return results
280
281
282def get_all_instances(include_fastboot=False):
283    """Create AndroidDevice instances for all attached android devices.
284
285    Args:
286        include_fastboot: Whether to include devices in bootloader mode or not.
287
288    Returns:
289        A list of AndroidDevice objects each representing an android device
290        attached to the computer.
291    """
292    if include_fastboot:
293        serial_list = list_adb_devices() + list_fastboot_devices()
294        return get_instances(serial_list)
295    return get_instances(list_adb_devices())
296
297
298def filter_devices(ads, func):
299    """Finds the AndroidDevice instances from a list that match certain
300    conditions.
301
302    Args:
303        ads: A list of AndroidDevice instances.
304        func: A function that takes an AndroidDevice object and returns True
305            if the device satisfies the filter condition.
306
307    Returns:
308        A list of AndroidDevice instances that satisfy the filter condition.
309    """
310    results = []
311    for ad in ads:
312        if func(ad):
313            results.append(ad)
314    return results
315
316
317def get_device(ads, **kwargs):
318    """Finds a unique AndroidDevice instance from a list that has specific
319    attributes of certain values.
320
321    Example:
322        get_device(android_devices, label="foo", phone_number="1234567890")
323        get_device(android_devices, model="angler")
324
325    Args:
326        ads: A list of AndroidDevice instances.
327        kwargs: keyword arguments used to filter AndroidDevice instances.
328
329    Returns:
330        The target AndroidDevice instance.
331
332    Raises:
333        AndroidDeviceError is raised if none or more than one device is
334        matched.
335    """
336
337    def _get_device_filter(ad):
338        for k, v in kwargs.items():
339            if not hasattr(ad, k):
340                return False
341            elif getattr(ad, k) != v:
342                return False
343        return True
344
345    filtered = filter_devices(ads, _get_device_filter)
346    if not filtered:
347        raise ValueError(
348            "Could not find a target device that matches condition: %s." %
349            kwargs)
350    elif len(filtered) == 1:
351        return filtered[0]
352    else:
353        serials = [ad.serial for ad in filtered]
354        raise ValueError("More than one device matched: %s" % serials)
355
356
357def take_bug_reports(ads, test_name, begin_time):
358    """Takes bug reports on a list of android devices.
359
360    If you want to take a bug report, call this function with a list of
361    android_device objects in on_fail. But reports will be taken on all the
362    devices in the list concurrently. Bug report takes a relative long
363    time to take, so use this cautiously.
364
365    Args:
366        ads: A list of AndroidDevice instances.
367        test_name: Name of the test case that triggered this bug report.
368        begin_time: Logline format timestamp taken when the test started.
369    """
370
371    def take_br(test_name, begin_time, ad):
372        ad.take_bug_report(test_name, begin_time)
373
374    args = [(test_name, begin_time, ad) for ad in ads]
375    utils.concurrent_exec(take_br, args)
376
377
378class AndroidDevice:
379    """Class representing an android device.
380
381    Each object of this class represents one Android device in ACTS, including
382    handles to adb, fastboot, and sl4a clients. In addition to direct adb
383    commands, this object also uses adb port forwarding to talk to the Android
384    device.
385
386    Attributes:
387        serial: A string that's the serial number of the Android device.
388        log_path: A string that is the path where all logs collected on this
389                  android device should be stored.
390        log: A logger adapted from root logger with added token specific to an
391             AndroidDevice instance.
392        adb_logcat_process: A process that collects the adb logcat.
393        adb: An AdbProxy object used for interacting with the device via adb.
394        fastboot: A FastbootProxy object used for interacting with the device
395                  via fastboot.
396        client_port: Preferred client port number on the PC host side for SL4A
397        forwarded_port: Preferred server port number forwarded from Android
398                        to the host PC via adb for SL4A connections
399        server_port: Preferred server port used by SL4A on Android device
400
401    """
402
403    def __init__(self,
404                 serial='',
405                 ssh_connection=None,
406                 client_port=0,
407                 forwarded_port=0,
408                 server_port=None):
409        self.serial = serial
410        # logging.log_path only exists when this is used in an ACTS test run.
411        log_path_base = getattr(logging, 'log_path', '/tmp/logs')
412        self.log_dir = 'AndroidDevice%s' % serial
413        self.log_path = os.path.join(log_path_base, self.log_dir)
414        self.client_port = client_port
415        self.forwarded_port = forwarded_port
416        self.server_port = server_port
417        self.log = tracelogger.TraceLogger(
418            AndroidDeviceLoggerAdapter(logging.getLogger(),
419                                       {'serial': serial}))
420        self._event_dispatchers = {}
421        self._services = []
422        self.register_service(services.AdbLogcatService(self))
423        self.register_service(services.Sl4aService(self))
424        self.adb_logcat_process = None
425        self.adb = adb.AdbProxy(serial, ssh_connection=ssh_connection)
426        self.fastboot = fastboot.FastbootProxy(serial,
427                                               ssh_connection=ssh_connection)
428        if not self.is_bootloader:
429            self.root_adb()
430        self._ssh_connection = ssh_connection
431        self.skip_sl4a = False
432        self.crash_report = None
433        self.data_accounting = collections.defaultdict(int)
434        self._sl4a_manager = sl4a_manager.create_sl4a_manager(self.adb)
435        self.last_logcat_timestamp = None
436        # Device info cache.
437        self._user_added_device_info = {}
438        self._sdk_api_level = None
439
440    def clean_up(self):
441        """Cleans up the AndroidDevice object and releases any resources it
442        claimed.
443        """
444        self.stop_services()
445        for service in self._services:
446            service.unregister()
447        self._services.clear()
448        if self._ssh_connection:
449            self._ssh_connection.close()
450
451    def recreate_services(self, serial):
452        """Clean up the AndroidDevice object and re-create adb/sl4a services.
453
454        Unregister the existing services and re-create adb and sl4a services,
455        call this method when the connection break after certain API call
456        (e.g., enable USB tethering by #startTethering)
457
458        Args:
459            serial: the serial number of the AndroidDevice
460        """
461        # Clean the old services
462        for service in self._services:
463            service.unregister()
464        self._services.clear()
465        if self._ssh_connection:
466            self._ssh_connection.close()
467        self._sl4a_manager.stop_service()
468
469        # Wait for old services to stop
470        time.sleep(5)
471
472        # Re-create the new adb and sl4a services
473        self.register_service(services.AdbLogcatService(self))
474        self.register_service(services.Sl4aService(self))
475        self.adb.wait_for_device(timeout=WAIT_FOR_DEVICE_TIMEOUT)
476        self.terminate_all_sessions()
477        self.start_services()
478
479    def register_service(self, service):
480        """Registers the service on the device. """
481        service.register()
482        self._services.append(service)
483
484    # TODO(angli): This function shall be refactored to accommodate all services
485    # and not have hard coded switch for SL4A when b/29157104 is done.
486    def start_services(self, skip_setup_wizard=True):
487        """Starts long running services on the android device.
488
489        1. Start adb logcat capture.
490        2. Start SL4A if not skipped.
491
492        Args:
493            skip_setup_wizard: Whether or not to skip the setup wizard.
494        """
495        if skip_setup_wizard:
496            self.exit_setup_wizard()
497
498        event_bus.post(android_events.AndroidStartServicesEvent(self))
499
500    def stop_services(self):
501        """Stops long running services on the android device.
502
503        Stop adb logcat and terminate sl4a sessions if exist.
504        """
505        event_bus.post(android_events.AndroidStopServicesEvent(self),
506                       ignore_errors=True)
507
508    def is_connected(self):
509        out = self.adb.devices()
510        devices = _parse_device_list(out, "device")
511        return self.serial in devices
512
513    @property
514    def build_info(self):
515        """Get the build info of this Android device, including build id and
516        build type.
517
518        This is not available if the device is in bootloader mode.
519
520        Returns:
521            A dict with the build info of this Android device, or None if the
522            device is in bootloader mode.
523        """
524        if self.is_bootloader:
525            self.log.error("Device is in fastboot mode, could not get build "
526                           "info.")
527            return
528
529        build_id = self.adb.getprop("ro.build.id")
530        incremental_build_id = self.adb.getprop("ro.build.version.incremental")
531        valid_build_id = False
532        for regex in RELEASE_ID_REGEXES:
533            if re.match(regex, build_id):
534                valid_build_id = True
535                break
536        if not valid_build_id:
537            build_id = incremental_build_id
538
539        info = {
540            "build_id": build_id,
541            "incremental_build_id": incremental_build_id,
542            "build_type": self.adb.getprop("ro.build.type"),
543            "build_date": self.adb.getprop("ro.build.date.utc"),
544            "baseband": self.adb.getprop("gsm.version.baseband")
545        }
546        return info
547
548    @property
549    def device_info(self):
550        """Information to be pulled into controller info.
551
552        The latest serial, model, and build_info are included. Additional info
553        can be added via `add_device_info`.
554        """
555        info = {
556            'serial': self.serial,
557            'model': self.model,
558            'build_info': self.build_info,
559            'user_added_info': self._user_added_device_info,
560            'flavor': self.flavor,
561            'revision': self.adb.getprop('ro.revision')
562        }
563        return info
564
565    def add_device_info(self, name, info):
566        """Add custom device info to the user_added_info section.
567
568        Adding the same info name the second time will override existing info.
569
570        Args:
571          name: string, name of this info.
572          info: serializable, content of the info.
573        """
574        self._user_added_device_info.update({name: info})
575
576    def sdk_api_level(self):
577        if self._sdk_api_level is not None:
578            return self._sdk_api_level
579        if self.is_bootloader:
580            self.log.error(
581                'Device is in fastboot mode. Cannot get build info.')
582            return
583        self._sdk_api_level = int(
584            self.adb.shell('getprop ro.build.version.sdk'))
585        return self._sdk_api_level
586
587    @property
588    def is_bootloader(self):
589        """True if the device is in bootloader mode.
590        """
591        return self.serial in list_fastboot_devices()
592
593    @property
594    def is_adb_root(self):
595        """True if adb is running as root for this device.
596        """
597        try:
598            return "0" == self.adb.shell("id -u")
599        except AdbError:
600            # Wait a bit and retry to work around adb flakiness for this cmd.
601            time.sleep(0.2)
602            return "0" == self.adb.shell("id -u")
603
604    @property
605    def model(self):
606        """The Android code name for the device."""
607        # If device is in bootloader mode, get mode name from fastboot.
608        if self.is_bootloader:
609            out = self.fastboot.getvar("product").strip()
610            # "out" is never empty because of the "total time" message fastboot
611            # writes to stderr.
612            lines = out.split('\n', 1)
613            if lines:
614                tokens = lines[0].split(' ')
615                if len(tokens) > 1:
616                    return tokens[1].lower()
617            return None
618        model = self.adb.getprop("ro.build.product").lower()
619        if model == "sprout":
620            return model
621        else:
622            return self.adb.getprop("ro.product.name").lower()
623
624    @property
625    def flavor(self):
626        """Returns the specific flavor of Android build the device is using."""
627        return self.adb.getprop("ro.build.flavor").lower()
628
629    @property
630    def droid(self):
631        """Returns the RPC Service of the first Sl4aSession created."""
632        if len(self._sl4a_manager.sessions) > 0:
633            session_id = sorted(self._sl4a_manager.sessions.keys())[0]
634            return self._sl4a_manager.sessions[session_id].rpc_client
635        else:
636            return None
637
638    @property
639    def ed(self):
640        """Returns the event dispatcher of the first Sl4aSession created."""
641        if len(self._sl4a_manager.sessions) > 0:
642            session_id = sorted(self._sl4a_manager.sessions.keys())[0]
643            return self._sl4a_manager.sessions[
644                session_id].get_event_dispatcher()
645        else:
646            return None
647
648    @property
649    def sl4a_sessions(self):
650        """Returns a dictionary of session ids to sessions."""
651        return list(self._sl4a_manager.sessions)
652
653    @property
654    def is_adb_logcat_on(self):
655        """Whether there is an ongoing adb logcat collection.
656        """
657        if self.adb_logcat_process:
658            if self.adb_logcat_process.is_running():
659                return True
660            else:
661                # if skip_sl4a is true, there is no sl4a session
662                # if logcat died due to device reboot and sl4a session has
663                # not restarted there is no droid.
664                if self.droid:
665                    self.droid.logI('Logcat died')
666                self.log.info("Logcat to %s died", self.log_path)
667                return False
668        return False
669
670    @property
671    def device_log_path(self):
672        """Returns the directory for all Android device logs for the current
673        test context and serial.
674        """
675        return context.get_current_context().get_full_output_path(self.serial)
676
677    def update_sdk_api_level(self):
678        self._sdk_api_level = None
679        self.sdk_api_level()
680
681    def load_config(self, config):
682        """Add attributes to the AndroidDevice object based on json config.
683
684        Args:
685            config: A dictionary representing the configs.
686
687        Raises:
688            AndroidDeviceError is raised if the config is trying to overwrite
689            an existing attribute.
690        """
691        for k, v in config.items():
692            # skip_sl4a value can be reset from config file
693            if hasattr(self, k) and k != "skip_sl4a":
694                raise errors.AndroidDeviceError(
695                    "Attempting to set existing attribute %s on %s" %
696                    (k, self.serial),
697                    serial=self.serial)
698            setattr(self, k, v)
699
700    def root_adb(self):
701        """Change adb to root mode for this device if allowed.
702
703        If executed on a production build, adb will not be switched to root
704        mode per security restrictions.
705        """
706        if self.is_adb_root:
707            return
708
709        for attempt in range(ADB_ROOT_RETRY_COUNT):
710            try:
711                self.log.debug('Enabling ADB root mode: attempt %d.' % attempt)
712                self.adb.root()
713            except AdbError:
714                if attempt == ADB_ROOT_RETRY_COUNT:
715                    raise
716                time.sleep(ADB_ROOT_RETRY_INTERVAL)
717        self.adb.wait_for_device()
718
719    def get_droid(self, handle_event=True):
720        """Create an sl4a connection to the device.
721
722        Return the connection handler 'droid'. By default, another connection
723        on the same session is made for EventDispatcher, and the dispatcher is
724        returned to the caller as well.
725        If sl4a server is not started on the device, try to start it.
726
727        Args:
728            handle_event: True if this droid session will need to handle
729                events.
730
731        Returns:
732            droid: Android object used to communicate with sl4a on the android
733                device.
734            ed: An optional EventDispatcher to organize events for this droid.
735
736        Examples:
737            Don't need event handling:
738            >>> ad = AndroidDevice()
739            >>> droid = ad.get_droid(False)
740
741            Need event handling:
742            >>> ad = AndroidDevice()
743            >>> droid, ed = ad.get_droid()
744        """
745        self.log.debug(
746            "Creating RPC client_port={}, forwarded_port={}, server_port={}".
747            format(self.client_port, self.forwarded_port, self.server_port))
748        session = self._sl4a_manager.create_session(
749            client_port=self.client_port,
750            forwarded_port=self.forwarded_port,
751            server_port=self.server_port)
752        droid = session.rpc_client
753        if handle_event:
754            ed = session.get_event_dispatcher()
755            return droid, ed
756        return droid
757
758    def get_package_pid(self, package_name):
759        """Gets the pid for a given package. Returns None if not running.
760        Args:
761            package_name: The name of the package.
762        Returns:
763            The first pid found under a given package name. None if no process
764            was found running the package.
765        Raises:
766            AndroidDeviceError if the output of the phone's process list was
767            in an unexpected format.
768        """
769        for cmd in ("ps -A", "ps"):
770            try:
771                out = self.adb.shell('%s | grep "S %s"' % (cmd, package_name),
772                                     ignore_status=True)
773                if package_name not in out:
774                    continue
775                try:
776                    pid = int(out.split()[1])
777                    self.log.info('apk %s has pid %s.', package_name, pid)
778                    return pid
779                except (IndexError, ValueError) as e:
780                    # Possible ValueError from string to int cast.
781                    # Possible IndexError from split.
782                    self.log.warning(
783                        'Command \"%s\" returned output line: '
784                        '\"%s\".\nError: %s', cmd, out, e)
785            except Exception as e:
786                self.log.warning(
787                    'Device fails to check if %s running with \"%s\"\n'
788                    'Exception %s', package_name, cmd, e)
789        self.log.debug("apk %s is not running", package_name)
790        return None
791
792    def get_dispatcher(self, droid):
793        """Return an EventDispatcher for an sl4a session
794
795        Args:
796            droid: Session to create EventDispatcher for.
797
798        Returns:
799            ed: An EventDispatcher for specified session.
800        """
801        return self._sl4a_manager.sessions[droid.uid].get_event_dispatcher()
802
803    def _is_timestamp_in_range(self, target, log_begin_time, log_end_time):
804        low = acts_logger.logline_timestamp_comparator(log_begin_time,
805                                                       target) <= 0
806        high = acts_logger.logline_timestamp_comparator(log_end_time,
807                                                        target) >= 0
808        return low and high
809
810    def cat_adb_log(self,
811                    tag,
812                    begin_time,
813                    end_time=None,
814                    dest_path="AdbLogExcerpts"):
815        """Takes an excerpt of the adb logcat log from a certain time point to
816        current time.
817
818        Args:
819            tag: An identifier of the time period, usually the name of a test.
820            begin_time: Epoch time of the beginning of the time period.
821            end_time: Epoch time of the ending of the time period, default None
822            dest_path: Destination path of the excerpt file.
823        """
824        log_begin_time = acts_logger.epoch_to_log_line_timestamp(begin_time)
825        if end_time is None:
826            log_end_time = acts_logger.get_log_line_timestamp()
827        else:
828            log_end_time = acts_logger.epoch_to_log_line_timestamp(end_time)
829        self.log.debug("Extracting adb log from logcat.")
830        logcat_path = os.path.join(self.device_log_path,
831                                   'adblog_%s_debug.txt' % self.serial)
832        if not os.path.exists(logcat_path):
833            self.log.warning("Logcat file %s does not exist." % logcat_path)
834            return
835        adb_excerpt_dir = os.path.join(self.log_path, dest_path)
836        os.makedirs(adb_excerpt_dir, exist_ok=True)
837        out_name = '%s,%s.txt' % (acts_logger.normalize_log_line_timestamp(
838            log_begin_time), self.serial)
839        tag_len = utils.MAX_FILENAME_LEN - len(out_name)
840        out_name = '%s,%s' % (tag[:tag_len], out_name)
841        adb_excerpt_path = os.path.join(adb_excerpt_dir, out_name)
842        with open(adb_excerpt_path, 'w', encoding='utf-8') as out:
843            in_file = logcat_path
844            with open(in_file, 'r', encoding='utf-8', errors='replace') as f:
845                while True:
846                    line = None
847                    try:
848                        line = f.readline()
849                        if not line:
850                            break
851                    except:
852                        continue
853                    line_time = line[:acts_logger.log_line_timestamp_len]
854                    if not acts_logger.is_valid_logline_timestamp(line_time):
855                        continue
856                    if self._is_timestamp_in_range(line_time, log_begin_time,
857                                                   log_end_time):
858                        if not line.endswith('\n'):
859                            line += '\n'
860                        out.write(line)
861        return adb_excerpt_path
862
863    def search_logcat(self,
864                      matching_string,
865                      begin_time=None,
866                      end_time=None,
867                      logcat_path=None):
868        """Search logcat message with given string.
869
870        Args:
871            matching_string: matching_string to search.
872            begin_time: only the lines with time stamps later than begin_time
873                will be searched.
874            end_time: only the lines with time stamps earlier than end_time
875                will be searched.
876            logcat_path: the path of a specific file in which the search should
877                be performed. If None the path will be the default device log
878                path.
879
880        Returns:
881            A list of dictionaries with full log message, time stamp string,
882            time object and message ID. For example:
883            [{"log_message": "05-03 17:39:29.898   968  1001 D"
884                              "ActivityManager: Sending BOOT_COMPLETE user #0",
885              "time_stamp": "2017-05-03 17:39:29.898",
886              "datetime_obj": datetime object,
887              "message_id": None}]
888
889            [{"log_message": "08-12 14:26:42.611043  2360  2510 D RILJ    : "
890                             "[0853]< DEACTIVATE_DATA_CALL  [PHONE0]",
891              "time_stamp": "2020-08-12 14:26:42.611043",
892              "datetime_obj": datetime object},
893              "message_id": "0853"}]
894        """
895        if not logcat_path:
896            logcat_path = os.path.join(self.device_log_path,
897                                       'adblog_%s_debug.txt' % self.serial)
898        if not os.path.exists(logcat_path):
899            self.log.warning("Logcat file %s does not exist." % logcat_path)
900            return
901        output = job.run("grep '%s' %s" % (matching_string, logcat_path),
902                         ignore_status=True)
903        if not output.stdout or output.exit_status != 0:
904            return []
905        if begin_time:
906            if not isinstance(begin_time, datetime):
907                log_begin_time = acts_logger.epoch_to_log_line_timestamp(
908                    begin_time)
909                begin_time = datetime.strptime(log_begin_time,
910                                               "%Y-%m-%d %H:%M:%S.%f")
911        if end_time:
912            if not isinstance(end_time, datetime):
913                log_end_time = acts_logger.epoch_to_log_line_timestamp(
914                    end_time)
915                end_time = datetime.strptime(log_end_time,
916                                             "%Y-%m-%d %H:%M:%S.%f")
917        result = []
918        logs = re.findall(r'(\S+\s\S+)(.*)', output.stdout)
919        for log in logs:
920            time_stamp = log[0]
921            time_obj = datetime.strptime(time_stamp, "%Y-%m-%d %H:%M:%S.%f")
922
923            if begin_time and time_obj < begin_time:
924                continue
925
926            if end_time and time_obj > end_time:
927                continue
928
929            res = re.findall(r'.*\[(\d+)\]', log[1])
930            try:
931                message_id = res[0]
932            except:
933                message_id = None
934
935            result.append({
936                "log_message": "".join(log),
937                "time_stamp": time_stamp,
938                "datetime_obj": time_obj,
939                "message_id": message_id
940            })
941        return result
942
943    def start_adb_logcat(self):
944        """Starts a standing adb logcat collection in separate subprocesses and
945        save the logcat in a file.
946        """
947        if self.is_adb_logcat_on:
948            self.log.warning(
949                'Android device %s already has a running adb logcat thread. ' %
950                self.serial)
951            return
952        # Disable adb log spam filter. Have to stop and clear settings first
953        # because 'start' doesn't support --clear option before Android N.
954        self.adb.shell("logpersist.stop --clear", ignore_status=True)
955        self.adb.shell("logpersist.start", ignore_status=True)
956        if hasattr(self, 'adb_logcat_param'):
957            extra_params = self.adb_logcat_param
958        else:
959            extra_params = "-b all"
960
961        self.adb_logcat_process = logcat.create_logcat_keepalive_process(
962            self.serial, self.log_dir, extra_params)
963        self.adb_logcat_process.start()
964
965    def stop_adb_logcat(self):
966        """Stops the adb logcat collection subprocess.
967        """
968        if not self.is_adb_logcat_on:
969            self.log.warning(
970                'Android device %s does not have an ongoing adb logcat ' %
971                self.serial)
972            return
973        # Set the last timestamp to the current timestamp. This may cause
974        # a race condition that allows the same line to be logged twice,
975        # but it does not pose a problem for our logging purposes.
976        self.adb_logcat_process.stop()
977        self.adb_logcat_process = None
978
979    def get_apk_uid(self, apk_name):
980        """Get the uid of the given apk.
981
982        Args:
983        apk_name: Name of the package, e.g., com.android.phone.
984
985        Returns:
986        Linux UID for the apk.
987        """
988        output = self.adb.shell("dumpsys package %s | grep -e userId= -e appId=" % apk_name,
989                                ignore_status=True)
990        result = re.search(r"userId=(\d+)|appId=(\d+)", output)
991        if result:
992            return result.group(1) if result.group(1) else result.group(2)
993        else:
994            None
995
996    @record_api_usage
997    def get_apk_version(self, package_name):
998        """Get the version of the given apk.
999
1000        Args:
1001            package_name: Name of the package, e.g., com.android.phone.
1002
1003        Returns:
1004            Version of the given apk.
1005        """
1006        try:
1007            output = self.adb.shell("dumpsys package %s | grep versionName" %
1008                                    package_name)
1009            pattern = re.compile(r"versionName=(.+)", re.I)
1010            result = pattern.findall(output)
1011            if result:
1012                return result[0]
1013        except Exception as e:
1014            self.log.warning("Fail to get the version of package %s: %s",
1015                             package_name, e)
1016        self.log.debug("apk %s is not found", package_name)
1017        return None
1018
1019    def is_apk_installed(self, package_name):
1020        """Check if the given apk is already installed.
1021
1022        Args:
1023        package_name: Name of the package, e.g., com.android.phone.
1024
1025        Returns:
1026        True if package is installed. False otherwise.
1027        """
1028
1029        try:
1030            return bool(
1031                self.adb.shell(
1032                    '(pm list packages | grep -w "package:%s") || true' %
1033                    package_name))
1034
1035        except Exception as err:
1036            self.log.error(
1037                'Could not determine if %s is installed. '
1038                'Received error:\n%s', package_name, err)
1039            return False
1040
1041    def is_sl4a_installed(self):
1042        return self.is_apk_installed(SL4A_APK_NAME)
1043
1044    def is_apk_running(self, package_name):
1045        """Check if the given apk is running.
1046
1047        Args:
1048            package_name: Name of the package, e.g., com.android.phone.
1049
1050        Returns:
1051        True if package is installed. False otherwise.
1052        """
1053        for cmd in ("ps -A", "ps"):
1054            try:
1055                out = self.adb.shell('%s | grep "S %s"' % (cmd, package_name),
1056                                     ignore_status=True)
1057                if package_name in out:
1058                    self.log.info("apk %s is running", package_name)
1059                    return True
1060            except Exception as e:
1061                self.log.warning(
1062                    "Device fails to check is %s running by %s "
1063                    "Exception %s", package_name, cmd, e)
1064                continue
1065        self.log.debug("apk %s is not running", package_name)
1066        return False
1067
1068    def is_sl4a_running(self):
1069        return self.is_apk_running(SL4A_APK_NAME)
1070
1071    def force_stop_apk(self, package_name):
1072        """Force stop the given apk.
1073
1074        Args:
1075        package_name: Name of the package, e.g., com.android.phone.
1076
1077        Returns:
1078        True if package is installed. False otherwise.
1079        """
1080        try:
1081            self.adb.shell('am force-stop %s' % package_name,
1082                           ignore_status=True)
1083        except Exception as e:
1084            self.log.warning("Fail to stop package %s: %s", package_name, e)
1085
1086    def take_bug_report(self, test_name=None, begin_time=None):
1087        """Takes a bug report on the device and stores it in a file.
1088
1089        Args:
1090            test_name: Name of the test case that triggered this bug report.
1091            begin_time: Epoch time when the test started. If none is specified,
1092                the current time will be used.
1093        """
1094        self.adb.wait_for_device(timeout=WAIT_FOR_DEVICE_TIMEOUT)
1095        new_br = True
1096        try:
1097            stdout = self.adb.shell("bugreportz -v")
1098            # This check is necessary for builds before N, where adb shell's ret
1099            # code and stderr are not propagated properly.
1100            if "not found" in stdout:
1101                new_br = False
1102        except AdbError:
1103            new_br = False
1104        br_path = self.device_log_path
1105        os.makedirs(br_path, exist_ok=True)
1106        epoch = begin_time if begin_time else utils.get_current_epoch_time()
1107        time_stamp = acts_logger.normalize_log_line_timestamp(
1108            acts_logger.epoch_to_log_line_timestamp(epoch))
1109        out_name = "AndroidDevice%s_%s" % (self.serial, time_stamp)
1110        out_name = "%s.zip" % out_name if new_br else "%s.txt" % out_name
1111        full_out_path = os.path.join(br_path, out_name)
1112        # in case device restarted, wait for adb interface to return
1113        self.wait_for_boot_completion()
1114        if test_name:
1115            self.log.info("Taking bugreport for %s.", test_name)
1116        else:
1117            self.log.info("Taking bugreport.")
1118        if new_br:
1119            out = self.adb.shell("bugreportz", timeout=BUG_REPORT_TIMEOUT)
1120            if not out.startswith("OK"):
1121                raise errors.AndroidDeviceError(
1122                    'Failed to take bugreport on %s: %s' % (self.serial, out),
1123                    serial=self.serial)
1124            br_out_path = out.split(':')[1].strip().split()[0]
1125            self.adb.pull("%s %s" % (br_out_path, full_out_path))
1126        else:
1127            self.adb.bugreport(" > {}".format(full_out_path),
1128                               timeout=BUG_REPORT_TIMEOUT)
1129        if test_name:
1130            self.log.info("Bugreport for %s taken at %s.", test_name,
1131                          full_out_path)
1132        else:
1133            self.log.info("Bugreport taken at %s.", test_name, full_out_path)
1134        self.adb.wait_for_device(timeout=WAIT_FOR_DEVICE_TIMEOUT)
1135
1136    def get_file_names(self,
1137                       directory,
1138                       begin_time=None,
1139                       skip_files=[],
1140                       match_string=None):
1141        """Get files names with provided directory."""
1142        cmd = "find %s -type f" % directory
1143        if begin_time:
1144            current_time = utils.get_current_epoch_time()
1145            seconds = int(math.ceil((current_time - begin_time) / 1000.0))
1146            cmd = "%s -mtime -%ss" % (cmd, seconds)
1147        if match_string:
1148            cmd = "%s -iname %s" % (cmd, match_string)
1149        for skip_file in skip_files:
1150            cmd = "%s ! -iname %s" % (cmd, skip_file)
1151        out = self.adb.shell(cmd, ignore_status=True)
1152        if not out or "No such" in out or "Permission denied" in out or \
1153            "Not a directory" in out:
1154            return []
1155        files = out.split("\n")
1156        self.log.debug("Find files in directory %s: %s", directory, files)
1157        return files
1158
1159    @property
1160    def external_storage_path(self):
1161        """
1162        The $EXTERNAL_STORAGE path on the device. Most commonly set to '/sdcard'
1163        """
1164        return self.adb.shell('echo $EXTERNAL_STORAGE')
1165
1166    def file_exists(self, file_path):
1167        """Returns whether a file exists on a device.
1168
1169        Args:
1170            file_path: The path of the file to check for.
1171        """
1172        cmd = '(test -f %s && echo yes) || echo no' % file_path
1173        result = self.adb.shell(cmd)
1174        if result == 'yes':
1175            return True
1176        elif result == 'no':
1177            return False
1178        raise ValueError('Couldn\'t determine if %s exists. '
1179                         'Expected yes/no, got %s' % (file_path, result[cmd]))
1180
1181    def pull_files(self, device_paths, host_path=None):
1182        """Pull files from devices.
1183
1184        Args:
1185            device_paths: List of paths on the device to pull from.
1186            host_path: Destination path
1187        """
1188        if isinstance(device_paths, str):
1189            device_paths = [device_paths]
1190        if not host_path:
1191            host_path = self.log_path
1192        for device_path in device_paths:
1193            self.log.info('Pull from device: %s -> %s' %
1194                          (device_path, host_path))
1195            self.adb.pull("%s %s" % (device_path, host_path),
1196                          timeout=PULL_TIMEOUT)
1197
1198    def check_crash_report(self,
1199                           test_name=None,
1200                           begin_time=None,
1201                           log_crash_report=False):
1202        """check crash report on the device."""
1203        crash_reports = []
1204        for crash_path in CRASH_REPORT_PATHS:
1205            try:
1206                cmd = 'cd %s' % crash_path
1207                self.adb.shell(cmd)
1208            except Exception as e:
1209                self.log.debug("received exception %s", e)
1210                continue
1211            crashes = self.get_file_names(crash_path,
1212                                          skip_files=CRASH_REPORT_SKIPS,
1213                                          begin_time=begin_time)
1214            if crash_path == "/data/tombstones/" and crashes:
1215                tombstones = crashes[:]
1216                for tombstone in tombstones:
1217                    if self.adb.shell(
1218                            'cat %s | grep "crash_dump failed to dump process"'
1219                            % tombstone):
1220                        crashes.remove(tombstone)
1221            if crashes:
1222                crash_reports.extend(crashes)
1223        if crash_reports and log_crash_report:
1224            crash_log_path = os.path.join(self.device_log_path,
1225                                          "Crashes_%s" % self.serial)
1226            os.makedirs(crash_log_path, exist_ok=True)
1227            self.pull_files(crash_reports, crash_log_path)
1228        return crash_reports
1229
1230    def get_qxdm_logs(self, test_name="", begin_time=None):
1231        """Get qxdm logs."""
1232        # Sleep 10 seconds for the buffered log to be written in qxdm log file
1233        time.sleep(10)
1234        log_path = getattr(self, "qxdm_log_path", DEFAULT_QXDM_LOG_PATH)
1235        qxdm_logs = self.get_file_names(log_path,
1236                                        begin_time=begin_time,
1237                                        match_string="*.qmdl")
1238        if qxdm_logs:
1239            qxdm_log_path = os.path.join(self.device_log_path,
1240                                         "QXDM_%s" % self.serial)
1241            os.makedirs(qxdm_log_path, exist_ok=True)
1242
1243            self.log.info("Pull QXDM Log %s to %s", qxdm_logs, qxdm_log_path)
1244            self.pull_files(qxdm_logs, qxdm_log_path)
1245
1246            self.adb.pull("/firmware/image/qdsp6m.qdb %s" % qxdm_log_path,
1247                          timeout=PULL_TIMEOUT,
1248                          ignore_status=True)
1249            # Zip Folder
1250            utils.zip_directory('%s.zip' % qxdm_log_path, qxdm_log_path)
1251            shutil.rmtree(qxdm_log_path)
1252        else:
1253            self.log.error("Didn't find QXDM logs in %s." % log_path)
1254        if "Verizon" in self.adb.getprop("gsm.sim.operator.alpha"):
1255            omadm_log_path = os.path.join(self.device_log_path,
1256                                          "OMADM_%s" % self.serial)
1257            os.makedirs(omadm_log_path, exist_ok=True)
1258            self.log.info("Pull OMADM Log")
1259            self.adb.pull(
1260                "/data/data/com.android.omadm.service/files/dm/log/ %s" %
1261                omadm_log_path,
1262                timeout=PULL_TIMEOUT,
1263                ignore_status=True)
1264
1265    def get_sdm_logs(self, test_name="", begin_time=None):
1266        """Get sdm logs."""
1267        # Sleep 10 seconds for the buffered log to be written in sdm log file
1268        time.sleep(10)
1269        log_paths = [
1270            ALWAYS_ON_LOG_PATH,
1271            getattr(self, "sdm_log_path", DEFAULT_SDM_LOG_PATH)
1272        ]
1273        sdm_logs = []
1274        for path in log_paths:
1275            sdm_logs += self.get_file_names(path,
1276                                            begin_time=begin_time,
1277                                            match_string="*.sdm*")
1278        if sdm_logs:
1279            sdm_log_path = os.path.join(self.device_log_path,
1280                                        "SDM_%s" % self.serial)
1281            os.makedirs(sdm_log_path, exist_ok=True)
1282            self.log.info("Pull SDM Log %s to %s", sdm_logs, sdm_log_path)
1283            self.pull_files(sdm_logs, sdm_log_path)
1284        else:
1285            self.log.error("Didn't find SDM logs in %s." % log_path)
1286        if "Verizon" in self.adb.getprop("gsm.sim.operator.alpha"):
1287            omadm_log_path = os.path.join(self.device_log_path,
1288                                          "OMADM_%s" % self.serial)
1289            os.makedirs(omadm_log_path, exist_ok=True)
1290            self.log.info("Pull OMADM Log")
1291            self.adb.pull(
1292                "/data/data/com.android.omadm.service/files/dm/log/ %s" %
1293                omadm_log_path,
1294                timeout=PULL_TIMEOUT,
1295                ignore_status=True)
1296
1297    def start_new_session(self, max_connections=None, server_port=None):
1298        """Start a new session in sl4a.
1299
1300        Also caches the droid in a dict with its uid being the key.
1301
1302        Returns:
1303            An Android object used to communicate with sl4a on the android
1304                device.
1305
1306        Raises:
1307            Sl4aException: Something is wrong with sl4a and it returned an
1308            existing uid to a new session.
1309        """
1310        session = self._sl4a_manager.create_session(
1311            max_connections=max_connections, server_port=server_port)
1312
1313        self._sl4a_manager.sessions[session.uid] = session
1314        return session.rpc_client
1315
1316    def terminate_all_sessions(self):
1317        """Terminate all sl4a sessions on the AndroidDevice instance.
1318
1319        Terminate all sessions and clear caches.
1320        """
1321        self._sl4a_manager.terminate_all_sessions()
1322
1323    def run_iperf_client_nb(self,
1324                            server_host,
1325                            extra_args="",
1326                            timeout=IPERF_TIMEOUT,
1327                            log_file_path=None):
1328        """Start iperf client on the device asynchronously.
1329
1330        Return status as true if iperf client start successfully.
1331        And data flow information as results.
1332
1333        Args:
1334            server_host: Address of the iperf server.
1335            extra_args: A string representing extra arguments for iperf client,
1336                e.g. "-i 1 -t 30".
1337            log_file_path: The complete file path to log the results.
1338
1339        """
1340        cmd = "iperf3 -c {} {}".format(server_host, extra_args)
1341        if log_file_path:
1342            cmd += " --logfile {} &".format(log_file_path)
1343        self.adb.shell_nb(cmd)
1344
1345    def run_iperf_client(self,
1346                         server_host,
1347                         extra_args="",
1348                         timeout=IPERF_TIMEOUT):
1349        """Start iperf client on the device.
1350
1351        Return status as true if iperf client start successfully.
1352        And data flow information as results.
1353
1354        Args:
1355            server_host: Address of the iperf server.
1356            extra_args: A string representing extra arguments for iperf client,
1357                e.g. "-i 1 -t 30".
1358
1359        Returns:
1360            status: true if iperf client start successfully.
1361            results: results have data flow information
1362        """
1363        out = self.adb.shell("iperf3 -c {} {}".format(server_host, extra_args),
1364                             timeout=timeout)
1365        clean_out = out.split('\n')
1366        if "error" in clean_out[0].lower():
1367            return False, clean_out
1368        return True, clean_out
1369
1370    def run_iperf_server(self, extra_args=""):
1371        """Start iperf server on the device
1372
1373        Return status as true if iperf server started successfully.
1374
1375        Args:
1376            extra_args: A string representing extra arguments for iperf server.
1377
1378        Returns:
1379            status: true if iperf server started successfully.
1380            results: results have output of command
1381        """
1382        out = self.adb.shell("iperf3 -s {}".format(extra_args))
1383        clean_out = out.split('\n')
1384        if "error" in clean_out[0].lower():
1385            return False, clean_out
1386        return True, clean_out
1387
1388    def wait_for_boot_completion(self, timeout=900.0):
1389        """Waits for Android framework to broadcast ACTION_BOOT_COMPLETED.
1390
1391        Args:
1392            timeout: Seconds to wait for the device to boot. Default value is
1393            15 minutes.
1394        """
1395        timeout_start = time.time()
1396
1397        self.log.debug("ADB waiting for device")
1398        self.adb.wait_for_device(timeout=timeout)
1399        self.log.debug("Waiting for  sys.boot_completed")
1400        while time.time() < timeout_start + timeout:
1401            try:
1402                completed = self.adb.getprop("sys.boot_completed")
1403                if completed == '1':
1404                    self.log.debug("Device has rebooted")
1405                    return
1406            except AdbError:
1407                # adb shell calls may fail during certain period of booting
1408                # process, which is normal. Ignoring these errors.
1409                pass
1410            time.sleep(5)
1411        raise errors.AndroidDeviceError(
1412            'Device %s booting process timed out.' % self.serial,
1413            serial=self.serial)
1414
1415    def reboot(self,
1416               stop_at_lock_screen=False,
1417               timeout=180,
1418               wait_after_reboot_complete=1):
1419        """Reboots the device.
1420
1421        Terminate all sl4a sessions, reboot the device, wait for device to
1422        complete booting, and restart an sl4a session if restart_sl4a is True.
1423
1424        Args:
1425            stop_at_lock_screen: whether to unlock after reboot. Set to False
1426                if want to bring the device to reboot up to password locking
1427                phase. Sl4a checking need the device unlocked after rebooting.
1428            timeout: time in seconds to wait for the device to complete
1429                rebooting.
1430            wait_after_reboot_complete: time in seconds to wait after the boot
1431                completion.
1432        """
1433        if self.is_bootloader:
1434            self.fastboot.reboot()
1435            return
1436        self.stop_services()
1437        self.log.info("Rebooting")
1438        # Send ACTION_SHUTDOWN broadcast and wait for registered services
1439        self.adb.shell("am broadcast -a android.intent.action.ACTION_SHUTDOWN")
1440        time.sleep(5)
1441        self.adb.reboot()
1442
1443        timeout_start = time.time()
1444        # b/111791239: Newer versions of android sometimes return early after
1445        # `adb reboot` is called. This means subsequent calls may make it to
1446        # the device before the reboot goes through, return false positives for
1447        # getprops such as sys.boot_completed.
1448        while time.time() < timeout_start + timeout:
1449            try:
1450                self.adb.get_state()
1451                time.sleep(.1)
1452            except AdbError:
1453                # get_state will raise an error if the device is not found. We
1454                # want the device to be missing to prove the device has kicked
1455                # off the reboot.
1456                break
1457        self.wait_for_boot_completion(timeout=(timeout - time.time() +
1458                                               timeout_start))
1459
1460        self.log.debug('Wait for a while after boot completion.')
1461        time.sleep(wait_after_reboot_complete)
1462        self.root_adb()
1463        skip_sl4a = self.skip_sl4a
1464        self.skip_sl4a = self.skip_sl4a or stop_at_lock_screen
1465        self.start_services()
1466        self.skip_sl4a = skip_sl4a
1467
1468    def restart_runtime(self):
1469        """Restarts android runtime.
1470
1471        Terminate all sl4a sessions, restarts runtime, wait for framework
1472        complete restart, and restart an sl4a session if restart_sl4a is True.
1473        """
1474        self.stop_services()
1475        self.log.info("Restarting android runtime")
1476        self.adb.shell("stop")
1477        # Reset the boot completed flag before we restart the framework
1478        # to correctly detect when the framework has fully come up.
1479        self.adb.shell("setprop sys.boot_completed 0")
1480        self.adb.shell("start")
1481        self.wait_for_boot_completion()
1482        self.root_adb()
1483
1484        self.start_services()
1485
1486    def get_ipv4_address(self, interface='wlan0', timeout=5):
1487        for timer in range(0, timeout):
1488            try:
1489                ip_string = self.adb.shell('ifconfig %s|grep inet' % interface)
1490                break
1491            except adb.AdbError as e:
1492                if timer + 1 == timeout:
1493                    self.log.warning('Unable to find IP address for %s.' %
1494                                     interface)
1495                    return None
1496                else:
1497                    time.sleep(1)
1498        result = re.search('addr:(.*) Bcast', ip_string)
1499        if result != None:
1500            ip_address = result.group(1)
1501            try:
1502                socket.inet_aton(ip_address)
1503                return ip_address
1504            except socket.error:
1505                return None
1506        else:
1507            return None
1508
1509    def get_ipv4_gateway(self, timeout=5):
1510        for timer in range(0, timeout):
1511            try:
1512                gateway_string = self.adb.shell(
1513                    'dumpsys wifi | grep mDhcpResults')
1514                break
1515            except adb.AdbError as e:
1516                if timer + 1 == timeout:
1517                    self.log.warning('Unable to find gateway')
1518                    return None
1519                else:
1520                    time.sleep(1)
1521        result = re.search('Gateway (.*) DNS servers', gateway_string)
1522        if result != None:
1523            ipv4_gateway = result.group(1)
1524            try:
1525                socket.inet_aton(ipv4_gateway)
1526                return ipv4_gateway
1527            except socket.error:
1528                return None
1529        else:
1530            return None
1531
1532    @record_api_usage
1533    def send_keycode(self, keycode):
1534        self.adb.shell("input keyevent KEYCODE_%s" % keycode)
1535
1536    @record_api_usage
1537    def get_my_current_focus_window(self):
1538        """Get the current focus window on screen"""
1539        output = self.adb.shell(
1540            'dumpsys window displays | grep -E mCurrentFocus | grep -v null',
1541            ignore_status=True)
1542        if not output or "not found" in output or "Can't find" in output:
1543            result = ''
1544        else:
1545            result = output.split(' ')[-1].strip("}")
1546        self.log.debug("Current focus window is %s", result)
1547        return result
1548
1549    @record_api_usage
1550    def get_my_current_focus_app(self):
1551        """Get the current focus application"""
1552        dumpsys_cmd = [
1553            'dumpsys window | grep -E mFocusedApp',
1554            'dumpsys window displays | grep -E mFocusedApp'
1555        ]
1556        for cmd in dumpsys_cmd:
1557            output = self.adb.shell(cmd, ignore_status=True)
1558            if not output or "not found" in output or "Can't find" in output or (
1559                    "mFocusedApp=null" in output):
1560                result = ''
1561            else:
1562                result = output.split(' ')[-2]
1563                break
1564        self.log.debug("Current focus app is %s", result)
1565        return result
1566
1567    @record_api_usage
1568    def is_window_ready(self, window_name=None):
1569        current_window = self.get_my_current_focus_window()
1570        if window_name:
1571            return window_name in current_window
1572        return current_window and ENCRYPTION_WINDOW not in current_window
1573
1574    @record_api_usage
1575    def wait_for_window_ready(self,
1576                              window_name=None,
1577                              check_interval=5,
1578                              check_duration=60):
1579        elapsed_time = 0
1580        while elapsed_time < check_duration:
1581            if self.is_window_ready(window_name=window_name):
1582                return True
1583            time.sleep(check_interval)
1584            elapsed_time += check_interval
1585        self.log.info("Current focus window is %s",
1586                      self.get_my_current_focus_window())
1587        return False
1588
1589    @record_api_usage
1590    def is_user_setup_complete(self):
1591        return "1" in self.adb.shell("settings get secure user_setup_complete")
1592
1593    @record_api_usage
1594    def is_screen_awake(self):
1595        """Check if device screen is in sleep mode"""
1596        return "Awake" in self.adb.shell("dumpsys power | grep mWakefulness=")
1597
1598    @record_api_usage
1599    def is_screen_emergency_dialer(self):
1600        """Check if device screen is in emergency dialer mode"""
1601        return "EmergencyDialer" in self.get_my_current_focus_window()
1602
1603    @record_api_usage
1604    def is_screen_in_call_activity(self):
1605        """Check if device screen is in in-call activity notification"""
1606        return "InCallActivity" in self.get_my_current_focus_window()
1607
1608    @record_api_usage
1609    def is_setupwizard_on(self):
1610        """Check if device screen is in emergency dialer mode"""
1611        return "setupwizard" in self.get_my_current_focus_app()
1612
1613    @record_api_usage
1614    def is_screen_lock_enabled(self):
1615        """Check if screen lock is enabled"""
1616        cmd = ("dumpsys window policy | grep showing=")
1617        out = self.adb.shell(cmd, ignore_status=True)
1618        return "true" in out
1619
1620    @record_api_usage
1621    def is_waiting_for_unlock_pin(self):
1622        """Check if device is waiting for unlock pin to boot up"""
1623        current_window = self.get_my_current_focus_window()
1624        current_app = self.get_my_current_focus_app()
1625        if ENCRYPTION_WINDOW in current_window:
1626            self.log.info("Device is in CrpytKeeper window")
1627            return True
1628        if "StatusBar" in current_window and (
1629            (not current_app) or "FallbackHome" in current_app):
1630            self.log.info("Device is locked")
1631            return True
1632        return False
1633
1634    @record_api_usage
1635    def ensure_screen_on(self):
1636        """Ensure device screen is powered on"""
1637        if self.is_screen_lock_enabled():
1638            for _ in range(2):
1639                self.unlock_screen()
1640                time.sleep(1)
1641                if self.is_waiting_for_unlock_pin():
1642                    self.unlock_screen(password=DEFAULT_DEVICE_PASSWORD)
1643                    time.sleep(1)
1644                if not self.is_waiting_for_unlock_pin(
1645                ) and self.wait_for_window_ready():
1646                    return True
1647            return False
1648        else:
1649            self.wakeup_screen()
1650            return True
1651
1652    @record_api_usage
1653    def wakeup_screen(self):
1654        if not self.is_screen_awake():
1655            self.log.info("Screen is not awake, wake it up")
1656            self.send_keycode("WAKEUP")
1657
1658    @record_api_usage
1659    def go_to_sleep(self):
1660        if self.is_screen_awake():
1661            self.send_keycode("SLEEP")
1662
1663    @record_api_usage
1664    def send_keycode_number_pad(self, number):
1665        self.send_keycode("NUMPAD_%s" % number)
1666
1667    @record_api_usage
1668    def unlock_screen(self, password=None):
1669        self.log.info("Unlocking with %s", password or "swipe up")
1670        # Bring device to SLEEP so that unlock process can start fresh
1671        self.send_keycode("SLEEP")
1672        time.sleep(1)
1673        self.send_keycode("WAKEUP")
1674        if ENCRYPTION_WINDOW not in self.get_my_current_focus_app():
1675            self.send_keycode("MENU")
1676        if password:
1677            self.send_keycode("DEL")
1678            for number in password:
1679                self.send_keycode_number_pad(number)
1680            self.send_keycode("ENTER")
1681            self.send_keycode("BACK")
1682
1683    @record_api_usage
1684    def screenshot(self, name=""):
1685        """Take a screenshot on the device.
1686
1687        Args:
1688            name: additional information of screenshot on the file name.
1689        """
1690        if name:
1691            file_name = "%s_%s" % (DEFAULT_SCREENSHOT_PATH, name)
1692        file_name = "%s_%s.png" % (file_name, utils.get_current_epoch_time())
1693        self.ensure_screen_on()
1694        self.log.info("Log screenshot to %s", file_name)
1695        try:
1696            self.adb.shell("screencap -p %s" % file_name)
1697        except:
1698            self.log.error("Fail to log screenshot to %s", file_name)
1699
1700    @record_api_usage
1701    def exit_setup_wizard(self):
1702        # Handling Android TV's setupwizard is ignored for now.
1703        if 'feature:android.hardware.type.television' in self.adb.shell(
1704                'pm list features'):
1705            return
1706        if not self.is_user_setup_complete() or self.is_setupwizard_on():
1707            # b/116709539 need this to prevent reboot after skip setup wizard
1708            self.adb.shell("am start -a com.android.setupwizard.EXIT",
1709                           ignore_status=True)
1710            self.adb.shell("pm disable %s" %
1711                           self.get_setupwizard_package_name(),
1712                           ignore_status=True)
1713        # Wait up to 5 seconds for user_setup_complete to be updated
1714        end_time = time.time() + 5
1715        while time.time() < end_time:
1716            if self.is_user_setup_complete() or not self.is_setupwizard_on():
1717                return
1718
1719        # If fail to exit setup wizard, set local.prop and reboot
1720        if not self.is_user_setup_complete() and self.is_setupwizard_on():
1721            self.adb.shell("echo ro.test_harness=1 > /data/local.prop")
1722            self.adb.shell("chmod 644 /data/local.prop")
1723            self.reboot(stop_at_lock_screen=True)
1724
1725    @record_api_usage
1726    def get_setupwizard_package_name(self):
1727        """Finds setupwizard package/.activity
1728
1729        Bypass setupwizard or setupwraith depending on device.
1730
1731         Returns:
1732            packageName/.ActivityName
1733        """
1734        packages_to_skip = "'setupwizard|setupwraith'"
1735        android_package_name = "com.google.android"
1736        package = self.adb.shell(
1737            "pm list packages -f | grep -E {} | grep {}".format(
1738                packages_to_skip, android_package_name))
1739        wizard_package = package.split('=')[1]
1740        activity = package.split('=')[0].split('/')[-2]
1741        self.log.info("%s/.%sActivity" % (wizard_package, activity))
1742        return "%s/.%sActivity" % (wizard_package, activity)
1743
1744    @record_api_usage
1745    def push_system_file(self, src_file_path, dst_file_path, push_timeout=300):
1746        """Pushes a file onto the read-only file system.
1747
1748        For speed, the device is left in root mode after this call, and leaves
1749        verity disabled. To re-enable verity, call ensure_verity_enabled().
1750
1751        Args:
1752            src_file_path: The path to the system app to install.
1753            dst_file_path: The destination of the file.
1754            push_timeout: How long to wait for the push to finish.
1755        Returns:
1756            Whether or not the install was successful.
1757        """
1758        self.adb.ensure_root()
1759        try:
1760            self.ensure_verity_disabled()
1761            self.adb.remount()
1762            out = self.adb.push('%s %s' % (src_file_path, dst_file_path),
1763                                timeout=push_timeout)
1764            if 'error' in out:
1765                self.log.error('Unable to push system file %s to %s due to %s',
1766                               src_file_path, dst_file_path, out)
1767                return False
1768            return True
1769        except Exception as e:
1770            self.log.error('Unable to push system file %s to %s due to %s',
1771                           src_file_path, dst_file_path, e)
1772            return False
1773
1774    @record_api_usage
1775    def ensure_verity_enabled(self):
1776        """Ensures that verity is enabled.
1777
1778        If verity is not enabled, this call will reboot the phone. Note that
1779        this only works on debuggable builds.
1780        """
1781        user = self.adb.get_user_id()
1782        # The below properties will only exist if verity has been enabled.
1783        system_verity = self.adb.getprop('partition.system.verified')
1784        vendor_verity = self.adb.getprop('partition.vendor.verified')
1785        if not system_verity or not vendor_verity:
1786            self.adb.ensure_root()
1787            self.adb.enable_verity()
1788            self.reboot()
1789            self.adb.ensure_user(user)
1790
1791    @record_api_usage
1792    def ensure_verity_disabled(self):
1793        """Ensures that verity is disabled.
1794
1795        If verity is enabled, this call will reboot the phone.
1796        """
1797        user = self.adb.get_user_id()
1798        # The below properties will only exist if verity has been enabled.
1799        system_verity = self.adb.getprop('partition.system.verified')
1800        vendor_verity = self.adb.getprop('partition.vendor.verified')
1801        if system_verity or vendor_verity:
1802            self.adb.ensure_root()
1803            self.adb.disable_verity()
1804            self.reboot()
1805            self.adb.ensure_user(user)
1806
1807
1808class AndroidDeviceLoggerAdapter(logging.LoggerAdapter):
1809    def process(self, msg, kwargs):
1810        msg = "[AndroidDevice|%s] %s" % (self.extra["serial"], msg)
1811        return (msg, kwargs)
1812