1#!/usr/bin/env python3.4
2#
3#   Copyright 2016 - The Android Open Source Project
4#
5#   Licensed under the Apache License, Version 2.0 (the "License");
6#   you may not use this file except in compliance with the License.
7#   You may obtain a copy of the License at
8#
9#       http://www.apache.org/licenses/LICENSE-2.0
10#
11#   Unless required by applicable law or agreed to in writing, software
12#   distributed under the License is distributed on an "AS IS" BASIS,
13#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14#   See the License for the specific language governing permissions and
15#   limitations under the License.
16
17from builtins import str
18from builtins import open
19from datetime import datetime
20
21import collections
22import logging
23import math
24import os
25import re
26import socket
27import time
28
29from acts import logger as acts_logger
30from acts import signals
31from acts import tracelogger
32from acts import utils
33from acts.controllers import adb
34from acts.controllers import fastboot
35from acts.controllers.sl4a_lib import sl4a_manager
36from acts.controllers.utils_lib.ssh import connection
37from acts.controllers.utils_lib.ssh import settings
38
39ACTS_CONTROLLER_CONFIG_NAME = "AndroidDevice"
40ACTS_CONTROLLER_REFERENCE_NAME = "android_devices"
41
42ANDROID_DEVICE_PICK_ALL_TOKEN = "*"
43# Key name for adb logcat extra params in config file.
44ANDROID_DEVICE_ADB_LOGCAT_PARAM_KEY = "adb_logcat_param"
45ANDROID_DEVICE_EMPTY_CONFIG_MSG = "Configuration is empty, abort!"
46ANDROID_DEVICE_NOT_LIST_CONFIG_MSG = "Configuration should be a list, abort!"
47CRASH_REPORT_PATHS = ("/data/tombstones/", "/data/vendor/ramdump/",
48                      "/data/ramdump/", "/data/vendor/ssrdump",
49                      "/data/vendor/ramdump/bluetooth")
50CRASH_REPORT_SKIPS = ("RAMDUMP_RESERVED", "RAMDUMP_STATUS", "RAMDUMP_OUTPUT",
51                      "bluetooth")
52DEFAULT_QXDM_LOG_PATH = "/data/vendor/radio/diag_logs"
53BUG_REPORT_TIMEOUT = 1800
54PULL_TIMEOUT = 300
55PORT_RETRY_COUNT = 3
56IPERF_TIMEOUT = 60
57SL4A_APK_NAME = "com.googlecode.android_scripting"
58WAIT_FOR_DEVICE_TIMEOUT = 180
59ENCRYPTION_WINDOW = "CryptKeeper"
60DEFAULT_DEVICE_PASSWORD = "1111"
61RELEASE_ID_REGEXES = [re.compile(r'\w+\.\d+\.\d+'), re.compile(r'N\w+')]
62
63
64class AndroidDeviceError(signals.ControllerError):
65    pass
66
67
68class DoesNotExistError(AndroidDeviceError):
69    """Raised when something that does not exist is referenced.
70    """
71
72
73def create(configs):
74    """Creates AndroidDevice controller objects.
75
76    Args:
77        configs: A list of dicts, each representing a configuration for an
78                 Android device.
79
80    Returns:
81        A list of AndroidDevice objects.
82    """
83    if not configs:
84        raise AndroidDeviceError(ANDROID_DEVICE_EMPTY_CONFIG_MSG)
85    elif configs == ANDROID_DEVICE_PICK_ALL_TOKEN:
86        ads = get_all_instances()
87    elif not isinstance(configs, list):
88        raise AndroidDeviceError(ANDROID_DEVICE_NOT_LIST_CONFIG_MSG)
89    elif isinstance(configs[0], str):
90        # Configs is a list of serials.
91        ads = get_instances(configs)
92    else:
93        # Configs is a list of dicts.
94        ads = get_instances_with_configs(configs)
95
96    ads[0].log.info('The primary device under test is "%s".' % ads[0].serial)
97
98    for ad in ads:
99        if not ad.is_connected():
100            raise DoesNotExistError(("Android device %s is specified in config"
101                                     " but is not attached.") % ad.serial)
102    _start_services_on_ads(ads)
103    return ads
104
105
106def destroy(ads):
107    """Cleans up AndroidDevice objects.
108
109    Args:
110        ads: A list of AndroidDevice objects.
111    """
112    for ad in ads:
113        try:
114            ad.clean_up()
115        except:
116            ad.log.exception("Failed to clean up properly.")
117
118
119def get_info(ads):
120    """Get information on a list of AndroidDevice objects.
121
122    Args:
123        ads: A list of AndroidDevice objects.
124
125    Returns:
126        A list of dict, each representing info for an AndroidDevice objects.
127    """
128    device_info = []
129    for ad in ads:
130        info = {"serial": ad.serial, "model": ad.model}
131        info.update(ad.build_info)
132        device_info.append(info)
133    return device_info
134
135
136def get_post_job_info(ads):
137    """Returns the tracked build id to test_run_summary.json
138
139    Args:
140        ads: A list of AndroidDevice objects.
141
142    Returns:
143        A dict consisting of {'build_id': ads[0].build_info}
144    """
145    return 'Build Info', ads[0].build_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        if not ad.ensure_screen_on():
161            ad.log.error("User window cannot come up")
162            destroy(running_ads)
163            raise AndroidDeviceError("User window cannot come up")
164        if not ad.skip_sl4a and not ad.is_sl4a_installed():
165            ad.log.error("sl4a.apk is not installed")
166            destroy(running_ads)
167            raise AndroidDeviceError("The required sl4a.apk is not installed")
168        try:
169            ad.start_services(skip_sl4a=ad.skip_sl4a)
170        except:
171            ad.log.exception("Failed to start some services, abort!")
172            destroy(running_ads)
173            raise
174
175
176def _parse_device_list(device_list_str, key):
177    """Parses a byte string representing a list of devices. The string is
178    generated by calling either adb or fastboot.
179
180    Args:
181        device_list_str: Output of adb or fastboot.
182        key: The token that signifies a device in device_list_str.
183
184    Returns:
185        A list of android device serial numbers.
186    """
187    return re.findall(r"(\S+)\t%s" % key, device_list_str)
188
189
190def list_adb_devices():
191    """List all android devices connected to the computer that are detected by
192    adb.
193
194    Returns:
195        A list of android device serials. Empty if there's none.
196    """
197    out = adb.AdbProxy().devices()
198    return _parse_device_list(out, "device")
199
200
201def list_fastboot_devices():
202    """List all android devices connected to the computer that are in in
203    fastboot mode. These are detected by fastboot.
204
205    Returns:
206        A list of android device serials. Empty if there's none.
207    """
208    out = fastboot.FastbootProxy().devices()
209    return _parse_device_list(out, "fastboot")
210
211
212def get_instances(serials):
213    """Create AndroidDevice instances from a list of serials.
214
215    Args:
216        serials: A list of android device serials.
217
218    Returns:
219        A list of AndroidDevice objects.
220    """
221    results = []
222    for s in serials:
223        results.append(AndroidDevice(s))
224    return results
225
226
227def get_instances_with_configs(configs):
228    """Create AndroidDevice instances from a list of json configs.
229
230    Each config should have the required key-value pair "serial".
231
232    Args:
233        configs: A list of dicts each representing the configuration of one
234            android device.
235
236    Returns:
237        A list of AndroidDevice objects.
238    """
239    results = []
240    for c in configs:
241        try:
242            serial = c.pop("serial")
243        except KeyError:
244            raise AndroidDeviceError(
245                "Required value 'serial' is missing in AndroidDevice config %s."
246                % c)
247        ssh_config = c.pop("ssh_config", None)
248        ssh_connection = None
249        if ssh_config is not None:
250            ssh_settings = settings.from_config(ssh_config)
251            ssh_connection = connection.SshConnection(ssh_settings)
252        ad = AndroidDevice(serial, ssh_connection=ssh_connection)
253        ad.load_config(c)
254        results.append(ad)
255    return results
256
257
258def get_all_instances(include_fastboot=False):
259    """Create AndroidDevice instances for all attached android devices.
260
261    Args:
262        include_fastboot: Whether to include devices in bootloader mode or not.
263
264    Returns:
265        A list of AndroidDevice objects each representing an android device
266        attached to the computer.
267    """
268    if include_fastboot:
269        serial_list = list_adb_devices() + list_fastboot_devices()
270        return get_instances(serial_list)
271    return get_instances(list_adb_devices())
272
273
274def filter_devices(ads, func):
275    """Finds the AndroidDevice instances from a list that match certain
276    conditions.
277
278    Args:
279        ads: A list of AndroidDevice instances.
280        func: A function that takes an AndroidDevice object and returns True
281            if the device satisfies the filter condition.
282
283    Returns:
284        A list of AndroidDevice instances that satisfy the filter condition.
285    """
286    results = []
287    for ad in ads:
288        if func(ad):
289            results.append(ad)
290    return results
291
292
293def get_device(ads, **kwargs):
294    """Finds a unique AndroidDevice instance from a list that has specific
295    attributes of certain values.
296
297    Example:
298        get_device(android_devices, label="foo", phone_number="1234567890")
299        get_device(android_devices, model="angler")
300
301    Args:
302        ads: A list of AndroidDevice instances.
303        kwargs: keyword arguments used to filter AndroidDevice instances.
304
305    Returns:
306        The target AndroidDevice instance.
307
308    Raises:
309        AndroidDeviceError is raised if none or more than one device is
310        matched.
311    """
312
313    def _get_device_filter(ad):
314        for k, v in kwargs.items():
315            if not hasattr(ad, k):
316                return False
317            elif getattr(ad, k) != v:
318                return False
319        return True
320
321    filtered = filter_devices(ads, _get_device_filter)
322    if not filtered:
323        raise AndroidDeviceError(
324            "Could not find a target device that matches condition: %s." %
325            kwargs)
326    elif len(filtered) == 1:
327        return filtered[0]
328    else:
329        serials = [ad.serial for ad in filtered]
330        raise AndroidDeviceError("More than one device matched: %s" % serials)
331
332
333def take_bug_reports(ads, test_name, begin_time):
334    """Takes bug reports on a list of android devices.
335
336    If you want to take a bug report, call this function with a list of
337    android_device objects in on_fail. But reports will be taken on all the
338    devices in the list concurrently. Bug report takes a relative long
339    time to take, so use this cautiously.
340
341    Args:
342        ads: A list of AndroidDevice instances.
343        test_name: Name of the test case that triggered this bug report.
344        begin_time: Logline format timestamp taken when the test started.
345    """
346
347    def take_br(test_name, begin_time, ad):
348        ad.take_bug_report(test_name, begin_time)
349
350    args = [(test_name, begin_time, ad) for ad in ads]
351    utils.concurrent_exec(take_br, args)
352
353
354class AndroidDevice:
355    """Class representing an android device.
356
357    Each object of this class represents one Android device in ACTS, including
358    handles to adb, fastboot, and sl4a clients. In addition to direct adb
359    commands, this object also uses adb port forwarding to talk to the Android
360    device.
361
362    Attributes:
363        serial: A string that's the serial number of the Android device.
364        log_path: A string that is the path where all logs collected on this
365                  android device should be stored.
366        log: A logger adapted from root logger with added token specific to an
367             AndroidDevice instance.
368        adb_logcat_process: A process that collects the adb logcat.
369        adb_logcat_file_path: A string that's the full path to the adb logcat
370                              file collected, if any.
371        adb: An AdbProxy object used for interacting with the device via adb.
372        fastboot: A FastbootProxy object used for interacting with the device
373                  via fastboot.
374    """
375
376    def __init__(self, serial='', ssh_connection=None):
377        self.serial = serial
378        # logging.log_path only exists when this is used in an ACTS test run.
379        log_path_base = getattr(logging, 'log_path', '/tmp/logs')
380        self.log_path = os.path.join(log_path_base, 'AndroidDevice%s' % serial)
381        self.log = tracelogger.TraceLogger(
382            AndroidDeviceLoggerAdapter(logging.getLogger(), {
383                'serial': self.serial
384            }))
385        self._event_dispatchers = {}
386        self.adb_logcat_process = None
387        self.adb_logcat_file_path = None
388        self.adb = adb.AdbProxy(serial, ssh_connection=ssh_connection)
389        self.fastboot = fastboot.FastbootProxy(
390            serial, ssh_connection=ssh_connection)
391        if not self.is_bootloader:
392            self.root_adb()
393        self._ssh_connection = ssh_connection
394        self.skip_sl4a = False
395        self.crash_report = None
396        self.data_accounting = collections.defaultdict(int)
397        self._sl4a_manager = sl4a_manager.Sl4aManager(self.adb)
398        self.last_logcat_timestamp = None
399
400    def clean_up(self):
401        """Cleans up the AndroidDevice object and releases any resources it
402        claimed.
403        """
404        self.stop_services()
405        if self._ssh_connection:
406            self._ssh_connection.close()
407
408    # TODO(angli): This function shall be refactored to accommodate all services
409    # and not have hard coded switch for SL4A when b/29157104 is done.
410    def start_services(self, skip_sl4a=False, skip_setup_wizard=True):
411        """Starts long running services on the android device.
412
413        1. Start adb logcat capture.
414        2. Start SL4A if not skipped.
415
416        Args:
417            skip_sl4a: Does not attempt to start SL4A if True.
418            skip_setup_wizard: Whether or not to skip the setup wizard.
419        """
420        if skip_setup_wizard:
421            self.exit_setup_wizard()
422        try:
423            self.start_adb_logcat()
424        except:
425            self.log.exception("Failed to start adb logcat!")
426            raise
427        if not skip_sl4a:
428            try:
429                droid, ed = self.get_droid()
430                ed.start()
431            except:
432                self.log.exception("Failed to start sl4a!")
433                raise
434
435    def stop_services(self):
436        """Stops long running services on the android device.
437
438        Stop adb logcat and terminate sl4a sessions if exist.
439        """
440        if self.is_adb_logcat_on:
441            self.stop_adb_logcat()
442        self.terminate_all_sessions()
443        self.stop_sl4a()
444
445    def is_connected(self):
446        out = self.adb.devices()
447        devices = _parse_device_list(out, "device")
448        return self.serial in devices
449
450    @property
451    def build_info(self):
452        """Get the build info of this Android device, including build id and
453        build type.
454
455        This is not available if the device is in bootloader mode.
456
457        Returns:
458            A dict with the build info of this Android device, or None if the
459            device is in bootloader mode.
460        """
461        if self.is_bootloader:
462            self.log.error("Device is in fastboot mode, could not get build "
463                           "info.")
464            return
465
466        build_id = self.adb.getprop("ro.build.id")
467        valid_build_id = False
468        for regex in RELEASE_ID_REGEXES:
469            if re.match(regex, build_id):
470                valid_build_id = True
471                break
472        if not valid_build_id:
473            build_id = self.adb.getprop("ro.build.version.incremental")
474
475        info = {
476            "build_id": build_id,
477            "build_type": self.adb.getprop("ro.build.type")
478        }
479        return info
480
481    @property
482    def is_bootloader(self):
483        """True if the device is in bootloader mode.
484        """
485        return self.serial in list_fastboot_devices()
486
487    @property
488    def is_adb_root(self):
489        """True if adb is running as root for this device.
490        """
491        try:
492            return "0" == self.adb.shell("id -u")
493        except adb.AdbError:
494            # Wait a bit and retry to work around adb flakiness for this cmd.
495            time.sleep(0.2)
496            return "0" == self.adb.shell("id -u")
497
498    @property
499    def model(self):
500        """The Android code name for the device."""
501        # If device is in bootloader mode, get mode name from fastboot.
502        if self.is_bootloader:
503            out = self.fastboot.getvar("product").strip()
504            # "out" is never empty because of the "total time" message fastboot
505            # writes to stderr.
506            lines = out.split('\n', 1)
507            if lines:
508                tokens = lines[0].split(' ')
509                if len(tokens) > 1:
510                    return tokens[1].lower()
511            return None
512        model = self.adb.getprop("ro.build.product").lower()
513        if model == "sprout":
514            return model
515        else:
516            return self.adb.getprop("ro.product.name").lower()
517
518    @property
519    def droid(self):
520        """Returns the RPC Service of the first Sl4aSession created."""
521        if len(self._sl4a_manager.sessions) > 0:
522            session_id = sorted(self._sl4a_manager.sessions.keys())[0]
523            return self._sl4a_manager.sessions[session_id].rpc_client
524        else:
525            return None
526
527    @property
528    def ed(self):
529        """Returns the event dispatcher of the first Sl4aSession created."""
530        if len(self._sl4a_manager.sessions) > 0:
531            session_id = sorted(self._sl4a_manager.sessions.keys())[0]
532            return self._sl4a_manager.sessions[
533                session_id].get_event_dispatcher()
534        else:
535            return None
536
537    @property
538    def sl4a_sessions(self):
539        """Returns a dictionary of session ids to sessions."""
540        return list(self._sl4a_manager.sessions)
541
542    @property
543    def is_adb_logcat_on(self):
544        """Whether there is an ongoing adb logcat collection.
545        """
546        if self.adb_logcat_process:
547            try:
548                utils._assert_subprocess_running(self.adb_logcat_process)
549                return True
550            except Exception:
551                # if skip_sl4a is true, there is no sl4a session
552                # if logcat died due to device reboot and sl4a session has
553                # not restarted there is no droid.
554                if self.droid:
555                    self.droid.logI('Logcat died')
556                self.log.info("Logcat to %s died", self.adb_logcat_file_path)
557                return False
558        return False
559
560    def load_config(self, config):
561        """Add attributes to the AndroidDevice object based on json config.
562
563        Args:
564            config: A dictionary representing the configs.
565
566        Raises:
567            AndroidDeviceError is raised if the config is trying to overwrite
568            an existing attribute.
569        """
570        for k, v in config.items():
571            # skip_sl4a value can be reset from config file
572            if hasattr(self, k) and k != "skip_sl4a":
573                raise AndroidDeviceError(
574                    "Attempting to set existing attribute %s on %s" %
575                    (k, self.serial))
576            setattr(self, k, v)
577
578    def root_adb(self):
579        """Change adb to root mode for this device if allowed.
580
581        If executed on a production build, adb will not be switched to root
582        mode per security restrictions.
583        """
584        self.adb.root()
585        self.adb.wait_for_device()
586
587    def get_droid(self, handle_event=True):
588        """Create an sl4a connection to the device.
589
590        Return the connection handler 'droid'. By default, another connection
591        on the same session is made for EventDispatcher, and the dispatcher is
592        returned to the caller as well.
593        If sl4a server is not started on the device, try to start it.
594
595        Args:
596            handle_event: True if this droid session will need to handle
597                events.
598
599        Returns:
600            droid: Android object used to communicate with sl4a on the android
601                device.
602            ed: An optional EventDispatcher to organize events for this droid.
603
604        Examples:
605            Don't need event handling:
606            >>> ad = AndroidDevice()
607            >>> droid = ad.get_droid(False)
608
609            Need event handling:
610            >>> ad = AndroidDevice()
611            >>> droid, ed = ad.get_droid()
612        """
613        session = self._sl4a_manager.create_session()
614        droid = session.rpc_client
615        if handle_event:
616            ed = session.get_event_dispatcher()
617            return droid, ed
618        return droid
619
620    def get_package_pid(self, package_name):
621        """Gets the pid for a given package. Returns None if not running.
622        Args:
623            package_name: The name of the package.
624        Returns:
625            The first pid found under a given package name. None if no process
626            was found running the package.
627        Raises:
628            AndroidDeviceError if the output of the phone's process list was
629            in an unexpected format.
630        """
631        for cmd in ("ps -A", "ps"):
632            try:
633                out = self.adb.shell(
634                    '%s | grep "S %s"' % (cmd, package_name),
635                    ignore_status=True)
636                if package_name not in out:
637                    continue
638                try:
639                    pid = int(out.split()[1])
640                    self.log.info('apk %s has pid %s.', package_name, pid)
641                    return pid
642                except (IndexError, ValueError) as e:
643                    # Possible ValueError from string to int cast.
644                    # Possible IndexError from split.
645                    self.log.warn('Command \"%s\" returned output line: '
646                                  '\"%s\".\nError: %s', cmd, out, e)
647            except Exception as e:
648                self.log.warn(
649                    'Device fails to check if %s running with \"%s\"\n'
650                    'Exception %s', package_name, cmd, e)
651        self.log.debug("apk %s is not running", package_name)
652        return None
653
654    def get_dispatcher(self, droid):
655        """Return an EventDispatcher for an sl4a session
656
657        Args:
658            droid: Session to create EventDispatcher for.
659
660        Returns:
661            ed: An EventDispatcher for specified session.
662        """
663        return self._sl4a_manager.sessions[droid.uid].get_event_dispatcher()
664
665    def _is_timestamp_in_range(self, target, log_begin_time, log_end_time):
666        low = acts_logger.logline_timestamp_comparator(log_begin_time,
667                                                       target) <= 0
668        high = acts_logger.logline_timestamp_comparator(log_end_time,
669                                                        target) >= 0
670        return low and high
671
672    def cat_adb_log(self, tag, begin_time):
673        """Takes an excerpt of the adb logcat log from a certain time point to
674        current time.
675
676        Args:
677            tag: An identifier of the time period, usualy the name of a test.
678            begin_time: Epoch time of the beginning of the time period.
679        """
680        log_begin_time = acts_logger.epoch_to_log_line_timestamp(begin_time)
681        if not self.adb_logcat_file_path:
682            raise AndroidDeviceError(
683                ("Attempting to cat adb log when none has"
684                 " been collected on Android device %s.") % self.serial)
685        log_end_time = acts_logger.get_log_line_timestamp()
686        self.log.debug("Extracting adb log from logcat.")
687        adb_excerpt_path = os.path.join(self.log_path, "AdbLogExcerpts")
688        utils.create_dir(adb_excerpt_path)
689        f_name = os.path.basename(self.adb_logcat_file_path)
690        out_name = f_name.replace("adblog,", "").replace(".txt", "")
691        out_name = ",{},{}.txt".format(log_begin_time, out_name)
692        tag_len = utils.MAX_FILENAME_LEN - len(out_name)
693        tag = tag[:tag_len]
694        out_name = tag + out_name
695        full_adblog_path = os.path.join(adb_excerpt_path, out_name)
696        with open(full_adblog_path, 'w', encoding='utf-8') as out:
697            in_file = self.adb_logcat_file_path
698            with open(in_file, 'r', encoding='utf-8', errors='replace') as f:
699                in_range = False
700                while True:
701                    line = None
702                    try:
703                        line = f.readline()
704                        if not line:
705                            break
706                    except:
707                        continue
708                    line_time = line[:acts_logger.log_line_timestamp_len]
709                    if not acts_logger.is_valid_logline_timestamp(line_time):
710                        continue
711                    if self._is_timestamp_in_range(line_time, log_begin_time,
712                                                   log_end_time):
713                        in_range = True
714                        if not line.endswith('\n'):
715                            line += '\n'
716                        out.write(line)
717                    else:
718                        if in_range:
719                            break
720
721    def start_adb_logcat(self, cont_logcat_file=False):
722        """Starts a standing adb logcat collection in separate subprocesses and
723        save the logcat in a file.
724
725        Args:
726            cont_logcat_file: Specifies whether to continue the previous logcat
727                              file.  This allows for start_adb_logcat to act
728                              as a restart logcat function if it is noticed
729                              logcat is no longer running.
730        """
731        if self.is_adb_logcat_on:
732            raise AndroidDeviceError(("Android device {} already has an adb "
733                                      "logcat thread going on. Cannot start "
734                                      "another one.").format(self.serial))
735        # Disable adb log spam filter. Have to stop and clear settings first
736        # because 'start' doesn't support --clear option before Android N.
737        self.adb.shell("logpersist.stop --clear")
738        self.adb.shell("logpersist.start")
739        if cont_logcat_file:
740            if self.droid:
741                self.droid.logI('Restarting logcat')
742            self.log.info(
743                'Restarting logcat on file %s' % self.adb_logcat_file_path)
744            logcat_file_path = self.adb_logcat_file_path
745        else:
746            f_name = "adblog,{},{}.txt".format(self.model, self.serial)
747            utils.create_dir(self.log_path)
748            logcat_file_path = os.path.join(self.log_path, f_name)
749        if hasattr(self, 'adb_logcat_param'):
750            extra_params = self.adb_logcat_param
751        else:
752            extra_params = "-b all"
753        if self.last_logcat_timestamp:
754            begin_at = '-T "%s"' % self.last_logcat_timestamp
755        else:
756            begin_at = '-T 1'
757        # TODO(markdr): Pull 'adb -s %SERIAL' from the AdbProxy object.
758        cmd = "adb -s {} logcat {} -v year {} >> {}".format(
759            self.serial, begin_at, extra_params, logcat_file_path)
760        self.adb_logcat_process = utils.start_standing_subprocess(cmd)
761        self.adb_logcat_file_path = logcat_file_path
762
763    def stop_adb_logcat(self):
764        """Stops the adb logcat collection subprocess.
765        """
766        if not self.is_adb_logcat_on:
767            raise AndroidDeviceError(
768                "Android device %s does not have an ongoing adb logcat collection."
769                % self.serial)
770        # Set the last timestamp to the current timestamp. This may cause
771        # a race condition that allows the same line to be logged twice,
772        # but it does not pose a problem for our logging purposes.
773        logcat_output = self.adb.logcat('-t 1 -v year')
774        next_line = logcat_output.find('\n')
775        self.last_logcat_timestamp = logcat_output[next_line + 1:
776                                                   next_line + 24]
777        utils.stop_standing_subprocess(self.adb_logcat_process)
778        self.adb_logcat_process = None
779
780    def get_apk_uid(self, apk_name):
781        """Get the uid of the given apk.
782
783        Args:
784        apk_name: Name of the package, e.g., com.android.phone.
785
786        Returns:
787        Linux UID for the apk.
788        """
789        output = self.adb.shell(
790            "dumpsys package %s | grep userId=" % apk_name, ignore_status=True)
791        result = re.search(r"userId=(\d+)", output)
792        if result:
793            return result.group(1)
794        else:
795            None
796
797    def is_apk_installed(self, package_name):
798        """Check if the given apk is already installed.
799
800        Args:
801        package_name: Name of the package, e.g., com.android.phone.
802
803        Returns:
804        True if package is installed. False otherwise.
805        """
806
807        try:
808            return bool(
809                self.adb.shell(
810                    'pm list packages | grep -w "package:%s"' % package_name))
811
812        except Exception as err:
813            self.log.error('Could not determine if %s is installed. '
814                           'Received error:\n%s', package_name, err)
815            return False
816
817    def is_sl4a_installed(self):
818        return self.is_apk_installed(SL4A_APK_NAME)
819
820    def is_apk_running(self, package_name):
821        """Check if the given apk is running.
822
823        Args:
824            package_name: Name of the package, e.g., com.android.phone.
825
826        Returns:
827        True if package is installed. False otherwise.
828        """
829        for cmd in ("ps -A", "ps"):
830            try:
831                out = self.adb.shell(
832                    '%s | grep "S %s"' % (cmd, package_name),
833                    ignore_status=True)
834                if package_name in out:
835                    self.log.info("apk %s is running", package_name)
836                    return True
837            except Exception as e:
838                self.log.warn("Device fails to check is %s running by %s "
839                              "Exception %s", package_name, cmd, e)
840                continue
841        self.log.debug("apk %s is not running", package_name)
842        return False
843
844    def is_sl4a_running(self):
845        return self.is_apk_running(SL4A_APK_NAME)
846
847    def force_stop_apk(self, package_name):
848        """Force stop the given apk.
849
850        Args:
851        package_name: Name of the package, e.g., com.android.phone.
852
853        Returns:
854        True if package is installed. False otherwise.
855        """
856        try:
857            self.adb.shell(
858                'am force-stop %s' % package_name, ignore_status=True)
859        except Exception as e:
860            self.log.warn("Fail to stop package %s: %s", package_name, e)
861
862    def stop_sl4a(self):
863        return self.force_stop_apk(SL4A_APK_NAME)
864
865    def start_sl4a(self):
866        self._sl4a_manager.start_sl4a_service()
867
868    def take_bug_report(self, test_name, begin_time):
869        """Takes a bug report on the device and stores it in a file.
870
871        Args:
872            test_name: Name of the test case that triggered this bug report.
873            begin_time: Epoch time when the test started.
874        """
875        self.adb.wait_for_device(timeout=WAIT_FOR_DEVICE_TIMEOUT)
876        new_br = True
877        try:
878            stdout = self.adb.shell("bugreportz -v")
879            # This check is necessary for builds before N, where adb shell's ret
880            # code and stderr are not propagated properly.
881            if "not found" in stdout:
882                new_br = False
883        except adb.AdbError:
884            new_br = False
885        br_path = os.path.join(self.log_path, test_name)
886        utils.create_dir(br_path)
887        time_stamp = acts_logger.normalize_log_line_timestamp(
888            acts_logger.epoch_to_log_line_timestamp(begin_time))
889        out_name = "AndroidDevice%s_%s" % (
890            self.serial, time_stamp.replace(" ", "_").replace(":", "-"))
891        out_name = "%s.zip" % out_name if new_br else "%s.txt" % out_name
892        full_out_path = os.path.join(br_path, out_name)
893        # in case device restarted, wait for adb interface to return
894        self.wait_for_boot_completion()
895        self.log.info("Taking bugreport for %s.", test_name)
896        if new_br:
897            out = self.adb.shell("bugreportz", timeout=BUG_REPORT_TIMEOUT)
898            if not out.startswith("OK"):
899                raise AndroidDeviceError("Failed to take bugreport on %s: %s" %
900                                         (self.serial, out))
901            br_out_path = out.split(':')[1].strip()
902            self.adb.pull("%s %s" % (br_out_path, full_out_path))
903        else:
904            self.adb.bugreport(
905                " > {}".format(full_out_path), timeout=BUG_REPORT_TIMEOUT)
906        self.log.info("Bugreport for %s taken at %s.", test_name,
907                      full_out_path)
908        self.adb.wait_for_device(timeout=WAIT_FOR_DEVICE_TIMEOUT)
909
910    def get_file_names(self,
911                       directory,
912                       begin_time=None,
913                       skip_files=[],
914                       match_string=None):
915        """Get files names with provided directory."""
916        cmd = "find %s -type f" % directory
917        if begin_time:
918            current_time = utils.get_current_epoch_time()
919            seconds = int(math.ceil((current_time - begin_time) / 1000.0))
920            cmd = "%s -mtime -%ss" % (cmd, seconds)
921        if match_string:
922            cmd = "%s -iname %s" % (cmd, match_string)
923        for skip_file in skip_files:
924            cmd = "%s ! -iname %s" % (cmd, skip_file)
925        out = self.adb.shell(cmd, ignore_status=True)
926        if not out or "No such" in out or "Permission denied" in out:
927            return []
928        files = out.split("\n")
929        self.log.debug("Find files in directory %s: %s", directory, files)
930        return files
931
932    def pull_files(self, files, remote_path=None):
933        """Pull files from devies."""
934        if not remote_path:
935            remote_path = self.log_path
936        for file_name in files:
937            self.adb.pull(
938                "%s %s" % (file_name, remote_path), timeout=PULL_TIMEOUT)
939
940    def check_crash_report(self,
941                           test_name=None,
942                           begin_time=None,
943                           log_crash_report=False):
944        """check crash report on the device."""
945        crash_reports = []
946        for crash_path in CRASH_REPORT_PATHS:
947            crashes = self.get_file_names(
948                crash_path,
949                skip_files=CRASH_REPORT_SKIPS,
950                begin_time=begin_time)
951            if crash_path == "/data/tombstones/" and crashes:
952                tombstones = crashes[:]
953                for tombstone in tombstones:
954                    if self.adb.shell(
955                            'cat %s | grep "crash_dump failed to dump process"'
956                            % tombstone):
957                        crashes.remove(tombstone)
958            if crashes:
959                crash_reports.extend(crashes)
960        if crash_reports and log_crash_report:
961            test_name = test_name or time.strftime("%Y-%m-%d-%Y-%H-%M-%S")
962            crash_log_path = os.path.join(self.log_path, test_name,
963                                          "Crashes_%s" % self.serial)
964            utils.create_dir(crash_log_path)
965            self.pull_files(crash_reports, crash_log_path)
966        return crash_reports
967
968    def get_qxdm_logs(self, test_name="", begin_time=None):
969        """Get qxdm logs."""
970        # Sleep 10 seconds for the buffered log to be written in qxdm log file
971        time.sleep(10)
972        log_path = getattr(self, "qxdm_log_path", DEFAULT_QXDM_LOG_PATH)
973        qxdm_logs = self.get_file_names(
974            log_path, begin_time=begin_time, match_string="*.qmdl")
975        if qxdm_logs:
976            qxdm_log_path = os.path.join(self.log_path, test_name,
977                                         "QXDM_%s" % self.serial)
978            utils.create_dir(qxdm_log_path)
979            self.log.info("Pull QXDM Log %s to %s", qxdm_logs, qxdm_log_path)
980            self.pull_files(qxdm_logs, qxdm_log_path)
981        else:
982            self.log.error("Didn't find QXDM logs in %s." % log_path)
983        if "Verizon" in self.adb.getprop("gsm.sim.operator.alpha"):
984            omadm_log_path = os.path.join(self.log_path, test_name,
985                                          "OMADM_%s" % self.serial)
986            utils.create_dir(omadm_log_path)
987            self.log.info("Pull OMADM Log")
988            self.adb.pull(
989                "/data/data/com.android.omadm.service/files/dm/log/ %s" %
990                omadm_log_path,
991                timeout=PULL_TIMEOUT,
992                ignore_status=True)
993
994    def start_new_session(self, max_connections=None, server_port=None):
995        """Start a new session in sl4a.
996
997        Also caches the droid in a dict with its uid being the key.
998
999        Returns:
1000            An Android object used to communicate with sl4a on the android
1001                device.
1002
1003        Raises:
1004            Sl4aException: Something is wrong with sl4a and it returned an
1005            existing uid to a new session.
1006        """
1007        session = self._sl4a_manager.create_session(
1008            max_connections=max_connections, server_port=server_port)
1009
1010        self._sl4a_manager.sessions[session.uid] = session
1011        return session.rpc_client
1012
1013    def terminate_all_sessions(self):
1014        """Terminate all sl4a sessions on the AndroidDevice instance.
1015
1016        Terminate all sessions and clear caches.
1017        """
1018        self._sl4a_manager.terminate_all_sessions()
1019
1020    def run_iperf_client_nb(self,
1021                            server_host,
1022                            extra_args="",
1023                            timeout=IPERF_TIMEOUT,
1024                            log_file_path=None):
1025        """Start iperf client on the device asynchronously.
1026
1027        Return status as true if iperf client start successfully.
1028        And data flow information as results.
1029
1030        Args:
1031            server_host: Address of the iperf server.
1032            extra_args: A string representing extra arguments for iperf client,
1033                e.g. "-i 1 -t 30".
1034            log_file_path: The complete file path to log the results.
1035
1036        """
1037        cmd = "iperf3 -c {} {}".format(server_host, extra_args)
1038        if log_file_path:
1039            cmd += " --logfile {} &".format(log_file_path)
1040        self.adb.shell_nb(cmd)
1041
1042    def run_iperf_client(self,
1043                         server_host,
1044                         extra_args="",
1045                         timeout=IPERF_TIMEOUT):
1046        """Start iperf client on the device.
1047
1048        Return status as true if iperf client start successfully.
1049        And data flow information as results.
1050
1051        Args:
1052            server_host: Address of the iperf server.
1053            extra_args: A string representing extra arguments for iperf client,
1054                e.g. "-i 1 -t 30".
1055
1056        Returns:
1057            status: true if iperf client start successfully.
1058            results: results have data flow information
1059        """
1060        out = self.adb.shell(
1061            "iperf3 -c {} {}".format(server_host, extra_args), timeout=timeout)
1062        clean_out = out.split('\n')
1063        if "error" in clean_out[0].lower():
1064            return False, clean_out
1065        return True, clean_out
1066
1067    def run_iperf_server(self, extra_args=""):
1068        """Start iperf server on the device
1069
1070        Return status as true if iperf server started successfully.
1071
1072        Args:
1073            extra_args: A string representing extra arguments for iperf server.
1074
1075        Returns:
1076            status: true if iperf server started successfully.
1077            results: results have output of command
1078        """
1079        out = self.adb.shell("iperf3 -s {}".format(extra_args))
1080        clean_out = out.split('\n')
1081        if "error" in clean_out[0].lower():
1082            return False, clean_out
1083        return True, clean_out
1084
1085    def wait_for_boot_completion(self):
1086        """Waits for Android framework to broadcast ACTION_BOOT_COMPLETED.
1087
1088        This function times out after 15 minutes.
1089        """
1090        timeout_start = time.time()
1091        timeout = 15 * 60
1092
1093        self.adb.wait_for_device(timeout=WAIT_FOR_DEVICE_TIMEOUT)
1094        while time.time() < timeout_start + timeout:
1095            try:
1096                completed = self.adb.getprop("sys.boot_completed")
1097                if completed == '1':
1098                    return
1099            except adb.AdbError:
1100                # adb shell calls may fail during certain period of booting
1101                # process, which is normal. Ignoring these errors.
1102                pass
1103            time.sleep(5)
1104        raise AndroidDeviceError(
1105            "Device %s booting process timed out." % self.serial)
1106
1107    def reboot(self, stop_at_lock_screen=False):
1108        """Reboots the device.
1109
1110        Terminate all sl4a sessions, reboot the device, wait for device to
1111        complete booting, and restart an sl4a session if restart_sl4a is True.
1112
1113        Args:
1114            stop_at_lock_screen: whether to unlock after reboot. Set to False
1115                if want to bring the device to reboot up to password locking
1116                phase. Sl4a checking need the device unlocked after rebooting.
1117
1118        Returns:
1119            None, sl4a session and event_dispatcher.
1120        """
1121        if self.is_bootloader:
1122            self.fastboot.reboot()
1123            return
1124        self.terminate_all_sessions()
1125        self.log.info("Rebooting")
1126        self.adb.reboot()
1127        self.wait_for_boot_completion()
1128        self.root_adb()
1129        if stop_at_lock_screen:
1130            return
1131        if not self.ensure_screen_on():
1132            self.log.error("User window cannot come up")
1133            raise AndroidDeviceError("User window cannot come up")
1134        self.start_services(self.skip_sl4a)
1135
1136    def restart_runtime(self):
1137        """Restarts android runtime.
1138
1139        Terminate all sl4a sessions, restarts runtime, wait for framework
1140        complete restart, and restart an sl4a session if restart_sl4a is True.
1141        """
1142        self.stop_services()
1143        self.log.info("Restarting android runtime")
1144        self.adb.shell("stop")
1145        self.adb.shell("start")
1146        self.wait_for_boot_completion()
1147        self.root_adb()
1148        if not self.ensure_screen_on():
1149            self.log.error("User window cannot come up")
1150            raise AndroidDeviceError("User window cannot come up")
1151        self.start_services(self.skip_sl4a)
1152
1153    def search_logcat(self, matching_string, begin_time=None):
1154        """Search logcat message with given string.
1155
1156        Args:
1157            matching_string: matching_string to search.
1158
1159        Returns:
1160            A list of dictionaries with full log message, time stamp string
1161            and time object. For example:
1162            [{"log_message": "05-03 17:39:29.898   968  1001 D"
1163                              "ActivityManager: Sending BOOT_COMPLETE user #0",
1164              "time_stamp": "2017-05-03 17:39:29.898",
1165              "datetime_obj": datetime object}]
1166        """
1167        cmd_option = '-b all -v year -d'
1168        if begin_time:
1169            log_begin_time = acts_logger.epoch_to_log_line_timestamp(
1170                begin_time)
1171            cmd_option = '%s -t "%s"' % (cmd_option, log_begin_time)
1172        out = self.adb.logcat(
1173            '%s | grep "%s"' % (cmd_option, matching_string),
1174            ignore_status=True)
1175        if not out: return []
1176        result = []
1177        logs = re.findall(r'(\S+\s\S+)(.*%s.*)' % re.escape(matching_string),
1178                          out)
1179        for log in logs:
1180            time_stamp = log[0]
1181            time_obj = datetime.strptime(time_stamp, "%Y-%m-%d %H:%M:%S.%f")
1182            result.append({
1183                "log_message": "".join(log),
1184                "time_stamp": time_stamp,
1185                "datetime_obj": time_obj
1186            })
1187        return result
1188
1189    def get_ipv4_address(self, interface='wlan0', timeout=5):
1190        for timer in range(0, timeout):
1191            try:
1192                ip_string = self.adb.shell('ifconfig %s|grep inet' % interface)
1193                break
1194            except adb.AdbError as e:
1195                if timer + 1 == timeout:
1196                    self.log.warning(
1197                        'Unable to find IP address for %s.' % interface)
1198                    return None
1199                else:
1200                    time.sleep(1)
1201        result = re.search('addr:(.*) Bcast', ip_string)
1202        if result != None:
1203            ip_address = result.group(1)
1204            try:
1205                socket.inet_aton(ip_address)
1206                return ip_address
1207            except socket.error:
1208                return None
1209        else:
1210            return None
1211
1212    def get_ipv4_gateway(self, timeout=5):
1213        for timer in range(0, timeout):
1214            try:
1215                gateway_string = self.adb.shell(
1216                    'dumpsys wifi | grep mDhcpResults')
1217                break
1218            except adb.AdbError as e:
1219                if timer + 1 == timeout:
1220                    self.log.warning('Unable to find gateway')
1221                    return None
1222                else:
1223                    time.sleep(1)
1224        result = re.search('Gateway (.*) DNS servers', gateway_string)
1225        if result != None:
1226            ipv4_gateway = result.group(1)
1227            try:
1228                socket.inet_aton(ipv4_gateway)
1229                return ipv4_gateway
1230            except socket.error:
1231                return None
1232        else:
1233            return None
1234
1235    def send_keycode(self, keycode):
1236        self.adb.shell("input keyevent KEYCODE_%s" % keycode)
1237
1238    def get_my_current_focus_window(self):
1239        """Get the current focus window on screen"""
1240        output = self.adb.shell(
1241            'dumpsys window windows | grep -E mCurrentFocus',
1242            ignore_status=True)
1243        if not output or "not found" in output or "Can't find" in output or (
1244                "mCurrentFocus=null" in output):
1245            result = ''
1246        else:
1247            result = output.split(' ')[-1].strip("}")
1248        self.log.debug("Current focus window is %s", result)
1249        return result
1250
1251    def get_my_current_focus_app(self):
1252        """Get the current focus application"""
1253        output = self.adb.shell(
1254            'dumpsys window windows | grep -E mFocusedApp', ignore_status=True)
1255        if not output or "not found" in output or "Can't find" in output or (
1256                "mFocusedApp=null" in output):
1257            result = ''
1258        else:
1259            result = output.split(' ')[-2]
1260        self.log.debug("Current focus app is %s", result)
1261        return result
1262
1263    def is_window_ready(self, window_name=None):
1264        current_window = self.get_my_current_focus_window()
1265        if window_name:
1266            return window_name in current_window
1267        return current_window and ENCRYPTION_WINDOW not in current_window
1268
1269    def wait_for_window_ready(self,
1270                              window_name=None,
1271                              check_interval=5,
1272                              check_duration=60):
1273        elapsed_time = 0
1274        while elapsed_time < check_duration:
1275            if self.is_window_ready(window_name=window_name):
1276                return True
1277            time.sleep(check_interval)
1278            elapsed_time += check_interval
1279        self.log.info("Current focus window is %s",
1280                      self.get_my_current_focus_window())
1281        return False
1282
1283    def is_user_setup_complete(self):
1284        return "1" in self.adb.shell("settings get secure user_setup_complete")
1285
1286    def is_screen_awake(self):
1287        """Check if device screen is in sleep mode"""
1288        return "Awake" in self.adb.shell("dumpsys power | grep mWakefulness=")
1289
1290    def is_screen_emergency_dialer(self):
1291        """Check if device screen is in emergency dialer mode"""
1292        return "EmergencyDialer" in self.get_my_current_focus_window()
1293
1294    def is_screen_in_call_activity(self):
1295        """Check if device screen is in in-call activity notification"""
1296        return "InCallActivity" in self.get_my_current_focus_window()
1297
1298    def is_setupwizard_on(self):
1299        """Check if device screen is in emergency dialer mode"""
1300        return "setupwizard" in self.get_my_current_focus_app()
1301
1302    def is_screen_lock_enabled(self):
1303        """Check if screen lock is enabled"""
1304        cmd = ("sqlite3 /data/system/locksettings.db .dump"" | grep lockscreen.password_type | grep -v alternate")
1305        out = self.adb.shell(cmd, ignore_status=True)
1306        if "unable to open" in out:
1307            self.root_adb()
1308            out = self.adb.shell(cmd, ignore_status=True)
1309        if ",0,'0'" not in out and out != "":
1310            self.log.info("Screen lock is enabled")
1311            return True
1312        return False
1313
1314    def is_waiting_for_unlock_pin(self):
1315        """Check if device is waiting for unlock pin to boot up"""
1316        current_window = self.get_my_current_focus_window()
1317        current_app = self.get_my_current_focus_app()
1318        if ENCRYPTION_WINDOW in current_window:
1319            self.log.info("Device is in CrpytKeeper window")
1320            return True
1321        if "StatusBar" in current_window and (
1322                (not current_app) or "FallbackHome" in current_app):
1323            self.log.info("Device is locked")
1324            return True
1325        return False
1326
1327    def ensure_screen_on(self):
1328        """Ensure device screen is powered on"""
1329        if self.is_screen_lock_enabled():
1330            for _ in range(2):
1331                self.unlock_screen()
1332                time.sleep(1)
1333                if self.is_waiting_for_unlock_pin():
1334                    self.unlock_screen(password=DEFAULT_DEVICE_PASSWORD)
1335                    time.sleep(1)
1336                if not self.is_waiting_for_unlock_pin(
1337                ) and self.wait_for_window_ready():
1338                    return True
1339            return False
1340        else:
1341            self.wakeup_screen()
1342            return True
1343
1344    def wakeup_screen(self):
1345        if not self.is_screen_awake():
1346            self.log.info("Screen is not awake, wake it up")
1347            self.send_keycode("WAKEUP")
1348
1349    def go_to_sleep(self):
1350        if self.is_screen_awake():
1351            self.send_keycode("SLEEP")
1352
1353    def send_keycode_number_pad(self, number):
1354        self.send_keycode("NUMPAD_%s" % number)
1355
1356    def unlock_screen(self, password=None):
1357        self.log.info("Unlocking with %s", password or "swipe up")
1358        # Bring device to SLEEP so that unlock process can start fresh
1359        self.send_keycode("SLEEP")
1360        time.sleep(1)
1361        self.send_keycode("WAKEUP")
1362        if ENCRYPTION_WINDOW not in self.get_my_current_focus_app():
1363            self.send_keycode("MENU")
1364        if password:
1365            self.send_keycode("DEL")
1366            for number in password:
1367                self.send_keycode_number_pad(number)
1368            self.send_keycode("ENTER")
1369            self.send_keycode("BACK")
1370
1371    def exit_setup_wizard(self):
1372        if not self.is_user_setup_complete() or self.is_setupwizard_on():
1373            self.adb.shell("pm disable %s" % self.get_setupwizard_package_name())
1374        # Wait up to 5 seconds for user_setup_complete to be updated
1375        for _ in range(5):
1376            if self.is_user_setup_complete() or not self.is_setupwizard_on():
1377                return
1378            time.sleep(1)
1379
1380        # If fail to exit setup wizard, set local.prop and reboot
1381        if not self.is_user_setup_complete() and self.is_setupwizard_on():
1382            self.adb.shell("echo ro.test_harness=1 > /data/local.prop")
1383            self.adb.shell("chmod 644 /data/local.prop")
1384            self.reboot(stop_at_lock_screen=True)
1385
1386    def get_setupwizard_package_name(self):
1387        """Finds setupwizard package/.activity
1388
1389         Returns:
1390            packageName/.ActivityName
1391         """
1392        package = self.adb.shell("pm list packages -f | grep setupwizard | grep com.google.android")
1393        wizard_package = re.split("=", package)[1]
1394        activity = re.search("wizard/(.*?).apk", package, re.IGNORECASE).groups()[0]
1395        self.log.info("%s/.%sActivity" % (wizard_package, activity))
1396        return "%s/.%sActivity" % (wizard_package, activity)
1397
1398
1399class AndroidDeviceLoggerAdapter(logging.LoggerAdapter):
1400    def process(self, msg, kwargs):
1401        msg = "[AndroidDevice|%s] %s" % (self.extra["serial"], msg)
1402        return (msg, kwargs)
1403