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