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