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