1#!/usr/bin/env python3
2#
3#   Copyright 2016 - Google
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"""
17    Base Class for Defining Common Telephony Test Functionality
18"""
19
20import logging
21import os
22import re
23import shutil
24import time
25
26from acts import asserts
27from acts import logger as acts_logger
28from acts import signals
29from acts.base_test import BaseTestClass
30from acts.controllers.android_device import DEFAULT_QXDM_LOG_PATH
31from acts.controllers.android_device import DEFAULT_SDM_LOG_PATH
32from acts.keys import Config
33from acts import records
34from acts import utils
35
36from acts.test_utils.tel.tel_subscription_utils import \
37    initial_set_up_for_subid_infomation
38from acts.test_utils.tel.tel_subscription_utils import \
39    set_default_sub_for_all_services
40from acts.test_utils.tel.tel_subscription_utils import get_subid_from_slot_index
41from acts.test_utils.tel.tel_test_utils import build_id_override
42from acts.test_utils.tel.tel_test_utils import disable_qxdm_logger
43from acts.test_utils.tel.tel_test_utils import enable_connectivity_metrics
44from acts.test_utils.tel.tel_test_utils import enable_radio_log_on
45from acts.test_utils.tel.tel_test_utils import ensure_phone_default_state
46from acts.test_utils.tel.tel_test_utils import ensure_phone_idle
47from acts.test_utils.tel.tel_test_utils import ensure_wifi_connected
48from acts.test_utils.tel.tel_test_utils import force_connectivity_metrics_upload
49from acts.test_utils.tel.tel_test_utils import get_operator_name
50from acts.test_utils.tel.tel_test_utils import get_screen_shot_log
51from acts.test_utils.tel.tel_test_utils import get_sim_state
52from acts.test_utils.tel.tel_test_utils import get_tcpdump_log
53from acts.test_utils.tel.tel_test_utils import multithread_func
54from acts.test_utils.tel.tel_test_utils import print_radio_info
55from acts.test_utils.tel.tel_test_utils import reboot_device
56from acts.test_utils.tel.tel_test_utils import recover_build_id
57from acts.test_utils.tel.tel_test_utils import run_multithread_func
58from acts.test_utils.tel.tel_test_utils import setup_droid_properties
59from acts.test_utils.tel.tel_test_utils import set_phone_screen_on
60from acts.test_utils.tel.tel_test_utils import set_phone_silent_mode
61from acts.test_utils.tel.tel_test_utils import set_qxdm_logger_command
62from acts.test_utils.tel.tel_test_utils import start_qxdm_logger
63from acts.test_utils.tel.tel_test_utils import start_qxdm_loggers
64from acts.test_utils.tel.tel_test_utils import start_sdm_loggers
65from acts.test_utils.tel.tel_test_utils import start_sdm_logger
66from acts.test_utils.tel.tel_test_utils import start_tcpdumps
67from acts.test_utils.tel.tel_test_utils import stop_qxdm_logger
68from acts.test_utils.tel.tel_test_utils import stop_sdm_loggers
69from acts.test_utils.tel.tel_test_utils import stop_sdm_logger
70from acts.test_utils.tel.tel_test_utils import stop_tcpdumps
71from acts.test_utils.tel.tel_test_utils import synchronize_device_time
72from acts.test_utils.tel.tel_test_utils import unlock_sim
73from acts.test_utils.tel.tel_test_utils import wait_for_sim_ready_by_adb
74from acts.test_utils.tel.tel_test_utils import wait_for_sims_ready_by_adb
75from acts.test_utils.tel.tel_test_utils import activate_wfc_on_device
76from acts.test_utils.tel.tel_test_utils import install_googleaccountutil_apk
77from acts.test_utils.tel.tel_test_utils import add_google_account
78from acts.test_utils.tel.tel_test_utils import install_googlefi_apk
79from acts.test_utils.tel.tel_test_utils import activate_google_fi_account
80from acts.test_utils.tel.tel_test_utils import check_google_fi_activated
81from acts.test_utils.tel.tel_test_utils import check_fi_apk_installed
82from acts.test_utils.tel.tel_test_utils import phone_switch_to_msim_mode
83from acts.test_utils.tel.tel_test_utils import activate_esim_using_suw
84from acts.test_utils.tel.tel_defines import PRECISE_CALL_STATE_LISTEN_LEVEL_BACKGROUND
85from acts.test_utils.tel.tel_defines import SINGLE_SIM_CONFIG, MULTI_SIM_CONFIG
86from acts.test_utils.tel.tel_defines import PRECISE_CALL_STATE_LISTEN_LEVEL_FOREGROUND
87from acts.test_utils.tel.tel_defines import PRECISE_CALL_STATE_LISTEN_LEVEL_RINGING
88from acts.test_utils.tel.tel_defines import SIM_STATE_ABSENT
89from acts.test_utils.tel.tel_defines import SIM_STATE_UNKNOWN
90from acts.test_utils.tel.tel_defines import WIFI_VERBOSE_LOGGING_ENABLED
91from acts.test_utils.tel.tel_defines import WIFI_VERBOSE_LOGGING_DISABLED
92from acts.test_utils.tel.tel_defines import INVALID_SUB_ID
93
94
95class TelephonyBaseTest(BaseTestClass):
96    # Use for logging in the test cases to facilitate
97    # faster log lookup and reduce ambiguity in logging.
98    @staticmethod
99    def tel_test_wrap(fn):
100        def _safe_wrap_test_case(self, *args, **kwargs):
101            test_id = "%s:%s:%s" % (self.__class__.__name__, self.test_name,
102                                    self.log_begin_time.replace(' ', '-'))
103            self.test_id = test_id
104            self.result_detail = ""
105            self.testsignal_details = ""
106            self.testsignal_extras = {}
107            tries = int(self.user_params.get("telephony_auto_rerun", 1))
108            for ad in self.android_devices:
109                ad.log_path = self.log_path
110            for i in range(tries + 1):
111                result = True
112                if i > 0:
113                    log_string = "[Test Case] RERUN %s" % self.test_name
114                    self.log.info(log_string)
115                    self._teardown_test(self.test_name)
116                    self._setup_test(self.test_name)
117                try:
118                    result = fn(self, *args, **kwargs)
119                except signals.TestFailure as e:
120                    self.testsignal_details = e.details
121                    self.testsignal_extras = e.extras
122                    result = False
123                except signals.TestSignal:
124                    raise
125                except Exception as e:
126                    self.log.exception(e)
127                    asserts.fail(self.result_detail)
128                if result is False:
129                    if i < tries:
130                        continue
131                else:
132                    break
133            if self.user_params.get("check_crash", True):
134                new_crash = ad.check_crash_report(self.test_name,
135                                                  self.begin_time, True)
136                if new_crash:
137                    msg = "Find new crash reports %s" % new_crash
138                    ad.log.error(msg)
139                    self.result_detail = "%s %s %s" % (self.result_detail,
140                                                       ad.serial, msg)
141                    result = False
142            if result is not False:
143                asserts.explicit_pass(self.result_detail)
144            else:
145                if self.result_detail:
146                    asserts.fail(self.result_detail)
147                else:
148                    asserts.fail(self.testsignal_details, self.testsignal_extras)
149
150        return _safe_wrap_test_case
151
152    def setup_class(self):
153        super().setup_class()
154        self.wifi_network_ssid = self.user_params.get(
155            "wifi_network_ssid") or self.user_params.get(
156                "wifi_network_ssid_2g") or self.user_params.get(
157                    "wifi_network_ssid_5g")
158        self.wifi_network_pass = self.user_params.get(
159            "wifi_network_pass") or self.user_params.get(
160                "wifi_network_pass_2g") or self.user_params.get(
161                    "wifi_network_ssid_5g")
162
163        self.log_path = getattr(logging, "log_path", None)
164        self.qxdm_log = self.user_params.get("qxdm_log", True)
165        self.sdm_log = self.user_params.get("sdm_log", False)
166        self.enable_radio_log_on = self.user_params.get(
167            "enable_radio_log_on", False)
168        self.cbrs_esim = self.user_params.get("cbrs_esim", False)
169        self.account_util = self.user_params.get("account_util", None)
170        self.save_passing_logs = self.user_params.get("save_passing_logs", False)
171        if isinstance(self.account_util, list):
172            self.account_util = self.account_util[0]
173        self.fi_util = self.user_params.get("fi_util", None)
174        if isinstance(self.fi_util, list):
175            self.fi_util = self.fi_util[0]
176        tasks = [(self._init_device, [ad]) for ad in self.android_devices]
177        multithread_func(self.log, tasks)
178        self.skip_reset_between_cases = self.user_params.get(
179            "skip_reset_between_cases", True)
180        self.log_path = getattr(logging, "log_path", None)
181        self.sim_config = {
182                            "config":SINGLE_SIM_CONFIG,
183                            "number_of_sims":1
184                        }
185
186        for ad in self.android_devices:
187            if hasattr(ad, "dsds"):
188                self.sim_config = {
189                                    "config":MULTI_SIM_CONFIG,
190                                    "number_of_sims":2
191                                }
192                break
193        if "anritsu_md8475a_ip_address" in self.user_params:
194            return
195        qxdm_log_mask_cfg = self.user_params.get("qxdm_log_mask_cfg", None)
196        if isinstance(qxdm_log_mask_cfg, list):
197            qxdm_log_mask_cfg = qxdm_log_mask_cfg[0]
198        if qxdm_log_mask_cfg and "dev/null" in qxdm_log_mask_cfg:
199            qxdm_log_mask_cfg = None
200        sim_conf_file = self.user_params.get("sim_conf_file")
201        if not sim_conf_file:
202            self.log.info("\"sim_conf_file\" is not provided test bed config!")
203        else:
204            if isinstance(sim_conf_file, list):
205                sim_conf_file = sim_conf_file[0]
206            # If the sim_conf_file is not a full path, attempt to find it
207            # relative to the config file.
208            if not os.path.isfile(sim_conf_file):
209                sim_conf_file = os.path.join(
210                    self.user_params[Config.key_config_path.value],
211                    sim_conf_file)
212                if not os.path.isfile(sim_conf_file):
213                    self.log.error("Unable to load user config %s ",
214                                   sim_conf_file)
215
216        tasks = [(self._setup_device, [ad, sim_conf_file, qxdm_log_mask_cfg])
217                 for ad in self.android_devices]
218        return multithread_func(self.log, tasks)
219
220    def _init_device(self, ad):
221        synchronize_device_time(ad)
222        ad.log_path = self.log_path
223        print_radio_info(ad)
224        unlock_sim(ad)
225        ad.wakeup_screen()
226        ad.adb.shell("input keyevent 82")
227
228    def wait_for_sim_ready(self,ad):
229        wait_for_sim_ready_on_sim_config = {
230              SINGLE_SIM_CONFIG : lambda:wait_for_sim_ready_by_adb(self.log,ad),
231              MULTI_SIM_CONFIG : lambda:wait_for_sims_ready_by_adb(self.log,ad)
232              }
233        if not wait_for_sim_ready_on_sim_config[self.sim_config["config"]]:
234            raise signals.TestAbortClass("unable to load the SIM")
235
236    def _setup_device(self, ad, sim_conf_file, qxdm_log_mask_cfg=None):
237        ad.qxdm_log = getattr(ad, "qxdm_log", self.qxdm_log)
238        ad.sdm_log = getattr(ad, "sdm_log", self.sdm_log)
239        if self.user_params.get("enable_connectivity_metrics", False):
240            enable_connectivity_metrics(ad)
241        if self.user_params.get("build_id_override", False):
242            build_postfix = self.user_params.get("build_id_postfix",
243                                                 "LAB_TEST")
244            build_id_override(
245                ad,
246                new_build_id=self.user_params.get("build_id_override_with",
247                                                  None),
248                postfix=build_postfix)
249        if self.enable_radio_log_on:
250            enable_radio_log_on(ad)
251        if "sdm" in ad.model or "msm" in ad.model or "kon" in ad.model:
252            phone_mode = "ssss"
253            if hasattr(ad, "mtp_dsds"):
254                phone_mode = "dsds"
255            if ad.adb.getprop("persist.radio.multisim.config") != phone_mode:
256                ad.adb.shell("setprop persist.radio.multisim.config %s" \
257                             % phone_mode)
258                reboot_device(ad)
259
260        stop_qxdm_logger(ad)
261        if ad.qxdm_log:
262            qxdm_log_mask = getattr(ad, "qxdm_log_mask", None)
263            if qxdm_log_mask_cfg:
264                qxdm_mask_path = self.user_params.get("qxdm_log_path",
265                                                      DEFAULT_QXDM_LOG_PATH)
266                ad.adb.shell("mkdir %s" % qxdm_mask_path)
267                ad.log.info("Push %s to %s", qxdm_log_mask_cfg, qxdm_mask_path)
268                ad.adb.push("%s %s" % (qxdm_log_mask_cfg, qxdm_mask_path))
269                mask_file_name = os.path.split(qxdm_log_mask_cfg)[-1]
270                qxdm_log_mask = os.path.join(qxdm_mask_path, mask_file_name)
271            set_qxdm_logger_command(ad, mask=qxdm_log_mask)
272            start_qxdm_logger(ad, utils.get_current_epoch_time())
273        elif ad.sdm_log:
274            start_sdm_logger(ad)
275        else:
276            disable_qxdm_logger(ad)
277        if not unlock_sim(ad):
278            raise signals.TestAbortClass("unable to unlock the SIM")
279
280        # eSIM enablement
281        if hasattr(ad, "fi_esim"):
282            if not ensure_wifi_connected(self.log, ad, self.wifi_network_ssid,
283                                         self.wifi_network_pass):
284                ad.log.error("Failed to connect to wifi")
285            if check_google_fi_activated(ad):
286                ad.log.info("Google Fi is already Activated")
287            else:
288                install_googleaccountutil_apk(ad, self.account_util)
289                add_google_account(ad)
290                install_googlefi_apk(ad, self.fi_util)
291                if not activate_google_fi_account(ad):
292                    ad.log.error("Failed to activate Fi")
293                check_google_fi_activated(ad)
294        if hasattr(ad, "dsds"):
295            sim_mode = ad.droid.telephonyGetPhoneCount()
296            if sim_mode == 1:
297                ad.log.info("Phone in Single SIM Mode")
298                if not phone_switch_to_msim_mode(ad):
299                    ad.log.error("Failed to switch to Dual SIM Mode")
300                    return False
301            elif sim_mode == 2:
302                ad.log.info("Phone already in Dual SIM Mode")
303        if get_sim_state(ad) in (SIM_STATE_ABSENT, SIM_STATE_UNKNOWN):
304            ad.log.info("Device has no or unknown SIM in it")
305            # eSIM needs activation
306            activate_esim_using_suw(ad)
307            ensure_phone_idle(self.log, ad)
308        elif self.user_params.get("Attenuator"):
309            ad.log.info("Device in chamber room")
310            ensure_phone_idle(self.log, ad)
311            setup_droid_properties(self.log, ad, sim_conf_file)
312        else:
313            self.wait_for_sim_ready(ad)
314            ensure_phone_default_state(self.log, ad)
315            setup_droid_properties(self.log, ad, sim_conf_file)
316
317        default_slot = getattr(ad, "default_slot", 0)
318        if get_subid_from_slot_index(ad.log, ad, default_slot) != INVALID_SUB_ID:
319            ad.log.info("Slot %s is the default slot.", default_slot)
320            set_default_sub_for_all_services(ad, default_slot)
321        else:
322            ad.log.warning("Slot %s is NOT a valid slot. Slot %s will be used by default.",
323                default_slot, 1-default_slot)
324            set_default_sub_for_all_services(ad, 1-default_slot)
325
326        # Activate WFC on Verizon, AT&T and Canada operators as per # b/33187374 &
327        # b/122327716
328        activate_wfc_on_device(self.log, ad)
329
330        # Sub ID setup
331        initial_set_up_for_subid_infomation(self.log, ad)
332
333        # If device is setup already, skip the following setup procedures
334        if getattr(ad, "telephony_test_setup", None):
335            return True
336
337        try:
338            ad.droid.wifiEnableVerboseLogging(WIFI_VERBOSE_LOGGING_ENABLED)
339        except Exception:
340            pass
341
342        # Disable Emergency alerts
343        # Set chrome browser start with no-first-run verification and
344        # disable-fre. Give permission to read from and write to storage.
345        for cmd in ("pm disable com.android.cellbroadcastreceiver",
346                    "pm grant com.android.chrome "
347                    "android.permission.READ_EXTERNAL_STORAGE",
348                    "pm grant com.android.chrome "
349                    "android.permission.WRITE_EXTERNAL_STORAGE",
350                    "rm /data/local/chrome-command-line",
351                    "am set-debug-app --persistent com.android.chrome",
352                    'echo "chrome --no-default-browser-check --no-first-run '
353                    '--disable-fre" > /data/local/tmp/chrome-command-line'):
354            ad.adb.shell(cmd, ignore_status=True)
355
356        # Curl for 2016/7 devices
357        if not getattr(ad, "curl_capable", False):
358            try:
359                out = ad.adb.shell("/data/curl --version")
360                if not out or "not found" in out:
361                    if int(ad.adb.getprop("ro.product.first_api_level")) >= 25:
362                        tel_data = self.user_params.get("tel_data", "tel_data")
363                        if isinstance(tel_data, list):
364                            tel_data = tel_data[0]
365                        curl_file_path = os.path.join(tel_data, "curl")
366                        if not os.path.isfile(curl_file_path):
367                            curl_file_path = os.path.join(
368                                self.user_params[Config.key_config_path.value],
369                                curl_file_path)
370                        if os.path.isfile(curl_file_path):
371                            ad.log.info("Pushing Curl to /data dir")
372                            ad.adb.push("%s /data" % (curl_file_path))
373                            ad.adb.shell(
374                                "chmod 777 /data/curl", ignore_status=True)
375                else:
376                    setattr(ad, "curl_capable", True)
377            except Exception:
378                ad.log.info("Failed to push curl on this device")
379
380        # Ensure that a test class starts from a consistent state that
381        # improves chances of valid network selection and facilitates
382        # logging.
383        try:
384            if not set_phone_screen_on(self.log, ad):
385                self.log.error("Failed to set phone screen-on time.")
386                return False
387            if not set_phone_silent_mode(self.log, ad):
388                self.log.error("Failed to set phone silent mode.")
389                return False
390            ad.droid.telephonyAdjustPreciseCallStateListenLevel(
391                PRECISE_CALL_STATE_LISTEN_LEVEL_FOREGROUND, True)
392            ad.droid.telephonyAdjustPreciseCallStateListenLevel(
393                PRECISE_CALL_STATE_LISTEN_LEVEL_RINGING, True)
394            ad.droid.telephonyAdjustPreciseCallStateListenLevel(
395                PRECISE_CALL_STATE_LISTEN_LEVEL_BACKGROUND, True)
396        except Exception as e:
397            self.log.error("Failure with %s", e)
398        setattr(ad, "telephony_test_setup", True)
399        return True
400
401    def _teardown_device(self, ad):
402        try:
403            stop_qxdm_logger(ad)
404            stop_sdm_logger(ad)
405        except Exception as e:
406            self.log.error("Failure with %s", e)
407        try:
408            ad.droid.disableDevicePassword()
409        except Exception as e:
410            self.log.error("Failure with %s", e)
411        if self.user_params.get("enable_connectivity_metrics", False):
412            if not ensure_wifi_connected(self.log, ad, self.wifi_network_ssid,
413                                         self.wifi_network_pass):
414                ad.log.error("Failed to connect to wifi")
415            force_connectivity_metrics_upload(ad)
416            time.sleep(30)
417        try:
418            ad.droid.wifiEnableVerboseLogging(WIFI_VERBOSE_LOGGING_DISABLED)
419        except Exception as e:
420            self.log.error("Failure with %s", e)
421        try:
422            if self.user_params.get("build_id_override",
423                                    False) and self.user_params.get(
424                                        "recover_build_id", False):
425                recover_build_id(ad)
426        except Exception as e:
427            self.log.error("Failure with %s", e)
428
429    def teardown_class(self):
430        tasks = [(self._teardown_device, [ad]) for ad in self.android_devices]
431        multithread_func(self.log, tasks)
432        return True
433
434    def setup_test(self):
435        if getattr(self, "qxdm_log", True):
436            if not self.user_params.get("qxdm_log_mask_cfg", None):
437                if "wfc" in self.test_name:
438                    for ad in self.android_devices:
439                        if not getattr(ad, "qxdm_logger_command", None) or (
440                                "IMS_DS_CNE_LnX_Golden.cfg" not in getattr(
441                                    ad, "qxdm_logger_command", "")):
442                            set_qxdm_logger_command(
443                                ad, "IMS_DS_CNE_LnX_Golden.cfg")
444                else:
445                    for ad in self.android_devices:
446                        if not getattr(ad, "qxdm_logger_command", None) or (
447                                "IMS_DS_CNE_LnX_Golden.cfg" in getattr(
448                                    ad, "qxdm_logger_command", "")):
449                            set_qxdm_logger_command(ad, None)
450            start_qxdm_loggers(self.log, self.android_devices, self.begin_time)
451        if getattr(self, "sdm_log", False):
452            start_sdm_loggers(self.log, self.android_devices)
453        if getattr(self, "tcpdump_log", False) or "wfc" in self.test_name:
454            mask = getattr(self, "tcpdump_mask", "all")
455            interface = getattr(self, "tcpdump_interface", "wlan0")
456            start_tcpdumps(
457                self.android_devices,
458                begin_time=self.begin_time,
459                interface=interface,
460                mask=mask)
461        else:
462            stop_tcpdumps(self.android_devices)
463        for ad in self.android_devices:
464            if self.skip_reset_between_cases:
465                ensure_phone_idle(self.log, ad)
466            else:
467                ensure_phone_default_state(self.log, ad)
468            for session in ad._sl4a_manager.sessions.values():
469                ed = session.get_event_dispatcher()
470                ed.clear_all_events()
471            output = ad.adb.logcat("-t 1")
472            match = re.search(r"\d+-\d+\s\d+:\d+:\d+.\d+", output)
473            if match:
474                ad.test_log_begin_time = match.group(0)
475
476    def teardown_test(self):
477        stop_tcpdumps(self.android_devices)
478
479    def on_fail(self, test_name, begin_time):
480        self._take_bug_report(test_name, begin_time)
481
482    def on_pass(self, test_name, begin_time):
483        if self.save_passing_logs:
484            self._take_bug_report(test_name, begin_time)
485
486    def _ad_take_extra_logs(self, ad, test_name, begin_time):
487        ad.adb.wait_for_device()
488        result = True
489
490        try:
491            # get tcpdump and screen shot log
492            get_tcpdump_log(ad, test_name, begin_time)
493            get_screen_shot_log(ad, test_name, begin_time)
494        except Exception as e:
495            ad.log.error("Exception error %s", e)
496            result = False
497
498        try:
499            ad.check_crash_report(test_name, begin_time, log_crash_report=True)
500        except Exception as e:
501            ad.log.error("Failed to check crash report for %s with error %s",
502                         test_name, e)
503            result = False
504
505        extra_qxdm_logs_in_seconds = self.user_params.get(
506            "extra_qxdm_logs_in_seconds", 60 * 3)
507        if getattr(ad, "qxdm_log", True):
508            # Gather qxdm log modified 3 minutes earlier than test start time
509            if begin_time:
510                qxdm_begin_time = begin_time - 1000 * extra_qxdm_logs_in_seconds
511            else:
512                qxdm_begin_time = None
513            try:
514                time.sleep(10)
515                ad.get_qxdm_logs(test_name, qxdm_begin_time)
516            except Exception as e:
517                ad.log.error("Failed to get QXDM log for %s with error %s",
518                             test_name, e)
519                result = False
520        if getattr(ad, "sdm_log", False):
521            # Gather sdm log modified 3 minutes earlier than test start time
522            if begin_time:
523                sdm_begin_time = begin_time - 1000 * extra_qxdm_logs_in_seconds
524            else:
525                sdm_begin_time = None
526            try:
527                time.sleep(10)
528                ad.get_sdm_logs(test_name, sdm_begin_time)
529            except Exception as e:
530                ad.log.error("Failed to get SDM log for %s with error %s",
531                             test_name, e)
532                result = False
533
534        return result
535
536    def _take_bug_report(self, test_name, begin_time):
537        if self._skip_bug_report(test_name):
538            return
539        dev_num = getattr(self, "number_of_devices", None) or len(
540            self.android_devices)
541        tasks = [(self._ad_take_bugreport, (ad, test_name, begin_time))
542                 for ad in self.android_devices[:dev_num]]
543        tasks.extend([(self._ad_take_extra_logs, (ad, test_name, begin_time))
544                      for ad in self.android_devices[:dev_num]])
545        run_multithread_func(self.log, tasks)
546        for ad in self.android_devices[:dev_num]:
547            if getattr(ad, "reboot_to_recover", False):
548                reboot_device(ad)
549                ad.reboot_to_recover = False
550        # Zip log folder
551        if not self.user_params.get("zip_log", False): return
552        src_dir = os.path.join(self.log_path, test_name)
553        os.makedirs(src_dir, exist_ok=True)
554        file_name = "%s_%s" % (src_dir, begin_time)
555        self.log.info("Zip folder %s to %s.zip", src_dir, file_name)
556        shutil.make_archive(file_name, "zip", src_dir)
557        shutil.rmtree(src_dir)
558
559    def _block_all_test_cases(self, tests, reason='Failed class setup'):
560        """Over-write _block_all_test_cases in BaseTestClass."""
561        for (i, (test_name, test_func)) in enumerate(tests):
562            signal = signals.TestFailure(reason)
563            record = records.TestResultRecord(test_name, self.TAG)
564            record.test_begin()
565            # mark all test cases as FAIL
566            record.test_fail(signal)
567            self.results.add_record(record)
568            # only gather bug report for the first test case
569            if i == 0:
570                self.on_fail(test_name, record.begin_time)
571
572    def get_stress_test_number(self):
573        """Gets the stress_test_number param from user params.
574
575        Gets the stress_test_number param. If absent, returns default 100.
576        """
577        return int(self.user_params.get("stress_test_number", 100))
578