#!/usr/bin/env python3.4
#
#   Copyright 2018 - Google
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
"""
    Connectivity Monitor and Telephony Troubleshooter Tests
"""

import os
import re
import time

from acts import signals
from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
from acts_contrib.test_utils.tel.tel_defines import CAPABILITY_VOLTE
from acts_contrib.test_utils.tel.tel_defines import CAPABILITY_VT
from acts_contrib.test_utils.tel.tel_defines import CAPABILITY_WFC
from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_FOR_STATE_CHANGE
from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
from acts_contrib.test_utils.tel.tel_defines import VT_STATE_BIDIRECTIONAL
from acts_contrib.test_utils.tel.tel_bootloader_utils import fastboot_wipe
from acts_contrib.test_utils.tel.tel_ims_utils import toggle_volte
from acts_contrib.test_utils.tel.tel_ims_utils import toggle_wfc
from acts_contrib.test_utils.tel.tel_ims_utils import wait_for_wfc_enabled
from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_2g
from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_voice_3g
from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_csfb
from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_iwlan
from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_volte
from acts_contrib.test_utils.tel.tel_test_utils import bring_up_connectivity_monitor
from acts_contrib.test_utils.tel.tel_test_utils import get_device_epoch_time
from acts_contrib.test_utils.tel.tel_test_utils import get_model_name
from acts_contrib.test_utils.tel.tel_test_utils import get_operator_name
from acts_contrib.test_utils.tel.tel_test_utils import get_outgoing_voice_sub_id
from acts_contrib.test_utils.tel.tel_test_utils import reboot_device
from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
from acts_contrib.test_utils.tel.tel_test_utils import trigger_modem_crash
from acts_contrib.test_utils.tel.tel_test_utils import trigger_modem_crash_by_modem
from acts_contrib.test_utils.tel.tel_video_utils import video_call_setup_teardown
from acts_contrib.test_utils.tel.tel_video_utils import phone_setup_video
from acts_contrib.test_utils.tel.tel_video_utils import is_phone_in_call_video_bidirectional
from acts_contrib.test_utils.tel.tel_voice_utils import call_setup_teardown
from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_2g
from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_iwlan
from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_volte
from acts_contrib.test_utils.tel.tel_voice_utils import last_call_drop_reason
from acts_contrib.test_utils.tel.tel_wifi_utils import ensure_wifi_connected
from acts_contrib.test_utils.tel.tel_wifi_utils import wifi_toggle_state

CALL_DROP_CODE_MAPPING = {
    373: "Radio Internal Error",
    175: "Invalid Transaction Identifier V02",
    159: "Temporary Failure",
    135: "Rejected by Network V02",
    118: "SS Not Available",
    115: "Call Barred V02",
    42: "Access Block V02",
    41: "Incompatible V02"
}

CONSECUTIVE_CALL_FAILS = 5
CALL_TROUBLE_THRESHOLD = 25
TROUBLES = {
    1: "WIFI_CALL_DROPS_IN_BAD_WIFI_SIGNAL",
    2: "WIFI_CALL_DROPS_IN_GOOD_WIFI_SIGNAL_ON_SPECIFIC_WIFI_NETWORK",
    3: "WIFI_CALL_DROPS_WITH_SPECIFIC_REASON_IN_GOOD_WIFI_SIGNAL",
    4: "WIFI_CALL_DROPS_WITH_RANDOM_FAILURES_IN_GOOD_WIFI_SIGNAL",
    5: "VOLTE_CALL_DROPS_IN_BAD_LTE_SIGNAL_AREAS",
    6: "VOLTE_CALL_DROPS_IN_GOOD_LTE_SIGNAL_AREAS",
    7: "CS_CALL_DROPS_IMS_DISABLED",
    8: "CS_CALL_DROPS_WFC_DISABLED",
    9: "CS_CALL_DROPS_IMS_REGISTRATION_FAILURES",
    10: "CS_CALL_DROPS_DURING_SRVCC",
    11: "CS_CALL_DROPS_IN_BAD_RF_CONDITIONS",
    12: "CS_CALL_DROPS_IN_GOOD_RF_CONDITIONS_WITH_SPECIFIC_REASON",
    13: "UNABLE_TO_TRIAGE"
}

ACTIONS = {
    1: "CHECK_BLUETOOTH",
    2: "CHECK_HEADSET",
    3: "SWITCH_FROM_WIFI_PREFERRED_TO_CELLULAR_PREFERRED",
    4: "SWITCH_FROM_CELLULAR_PREFERRED_TO_WIFI_PREFERRED",
    5: "ENABLE_ADVANCED_4G_CALLING",
    6: "DISABLE_ADVANCED_4G_CALLING",
    7: "TOGGLE_AIRPLANE_MODE_TWICE",
    8: "REBOOT_THE_PHONE",
    9: "ENABLE_WIFI_CALLING",
    10: "DISABLE_WIFI_CALLING",
    11: "DISABLE_AIRPLANE_MODE",
    12: "NONE"
}

IGNORED_CALL_DROP_REASONS = ["Radio Link Lost", "Media Timeout"]

CALL_DATA_LOGS = (
    "/data/data/com.google.android.connectivitymonitor/databases")

IGNORED_CALL_DROP_TRIGGERS = ["toggle_apm", "toggle_wifi"]


class TelLiveConnectivityMonitorBaseTest(TelephonyBaseTest):
    def setup_class(self):
        TelephonyBaseTest.setup_class(self)
        self.user_params["enable_connectivity_metrics"] = False
        self.user_params["telephony_auto_rerun"] = 0
        self.consecutive_failure_limit = 5

        self.dut = self.android_devices[0]
        self.ad_reference = self.android_devices[1]
        self.dut_model = get_model_name(self.dut)
        self.dut_operator = get_operator_name(self.log, self.dut)
        self.dut_subID = get_outgoing_voice_sub_id(self.dut)
        self.dut_capabilities = self.dut.telephony["subscription"][self.dut_subID].get("capabilities", [])
        self.dut_wfc_modes = self.dut.telephony["subscription"][self.dut_subID].get("wfc_modes", [])
        self.ad_reference_subID = get_outgoing_voice_sub_id(self.ad_reference)
        self.reference_capabilities = self.ad_reference.telephony["subscription"][self.ad_reference_subID].get(
            "capabilities", [])
        self.dut.log.info("DUT capabilities: %s", self.dut_capabilities)
        self.skip_reset_between_cases = False
        self.user_params["telephony_auto_rerun"] = 0
        self.number_of_devices = 1
        self.call_drop_override_code = self.user_params.get(
            "call_drop_override_code", 373)

    def setup_test(self):
        TelephonyBaseTest.setup_test(self)
        bring_up_connectivity_monitor(self.dut)
        ## Work around for WFC not working issue on 2018 devices
        if "Permissive" not in self.dut.adb.shell("su root getenforce"):
            self.dut.adb.shell("su root setenforce 0")

    def on_fail(self, test_name, begin_time):
        self.dut.log.info("Pulling %s", CALL_DATA_LOGS)
        log_path = os.path.join(self.dut.log_path, test_name,
                                "ConnectivityMonitorLogs_%s" % self.dut.serial)
        os.makedirs(log_path, exist_ok=True)
        self.dut.pull_files([CALL_DATA_LOGS], log_path)

        self._take_bug_report(test_name, begin_time)

    def teardown_test(self):
        self.set_drop_reason_override(override_code=None)
        TelephonyBaseTest.teardown_test(self)

    def connect_to_wifi(self):
        if not ensure_wifi_connected(self.log, self.dut,
                                     self.wifi_network_ssid,
                                     self.wifi_network_pass):
            self.dut.log.error("Fail to connected to WiFi")
            return False
        else:
            self.dut.log.info("Connected to WiFi")
            return True

    def is_wfc_enabled(self):
        return wait_for_wfc_enabled(self.log, self.dut)

    def enable_volte(self):
        if CAPABILITY_VOLTE not in self.dut_capabilities:
            raise signals.TestSkip("VoLTE is not supported, abort test.")
        toggle_volte(self.log, self.dut, True)

    def enable_wfc(self):
        if CAPABILITY_WFC not in self.dut_capabilities:
            raise signals.TestSkip("WFC is not supported, abort test.")
        toggle_wfc(self.log, self.dut, True)

    def disable_volte(self):
        if CAPABILITY_VOLTE not in self.dut_capabilities:
            raise signals.TestSkip("VoLTE is not supported, abort test.")
        toggle_volte(self.log, self.dut, False)

    def disable_wfc(self):
        if CAPABILITY_WFC not in self.dut_capabilities:
            raise signals.TestSkip("WFC is not supported, abort test.")
        toggle_wfc(self.log, self.dut, False)

    def setup_wfc_non_apm(self):
        if CAPABILITY_WFC not in self.dut_capabilities and (
                WFC_MODE_WIFI_PREFERRED not in self.dut_wfc_modes):
            raise signals.TestSkip(
                "WFC in non-APM is not supported, abort test.")
        if not phone_setup_iwlan(
                self.log, self.dut, False, WFC_MODE_WIFI_PREFERRED,
                self.wifi_network_ssid, self.wifi_network_pass):
            self.dut.log.error("Failed to setup WFC.")
            raise signals.TestFailure("Failed to setup WFC in non-APM")
        self.dut.log.info("Phone is in WFC enabled state.")
        return True

    def setup_wfc_apm(self):
        if CAPABILITY_WFC not in self.dut_capabilities:
            raise signals.TestSkip("WFC is not supported, abort test.")
        if not phone_setup_iwlan(self.log, self.dut, True,
                                 self.dut_wfc_modes[0], self.wifi_network_ssid,
                                 self.wifi_network_pass):
            self.dut.log.error("Failed to setup WFC.")
            raise signals.TestFailure("Failed to setup WFC in APM")
        self.dut.log.info("Phone is in WFC enabled state.")
        return True

    def setup_volte(self):
        if CAPABILITY_VOLTE not in self.dut_capabilities:
            raise signals.TestSkip("VoLTE is not supported, abort test.")
        if not phone_setup_volte(self.log, self.dut):
            self.dut.log.error("Phone failed to enable VoLTE.")
            raise signals.TestFailure("Failed to enable VoLTE")
        self.dut.log.info("Phone VOLTE is enabled successfully.")
        return True

    def setup_csfb(self):
        if not phone_setup_csfb(self.log, self.dut):
            self.dut.log.error("Phone failed to setup CSFB.")
            raise signals.TestFailure("Failed to setup CSFB")
        self.dut.log.info("Phone CSFB is enabled successfully.")
        return True

    def setup_3g(self):
        if not phone_setup_voice_3g(self.log, self.dut):
            self.dut.log.error("Phone failed to setup 3G.")
            raise signals.TestFailure("Faile to setup 3G")
        self.dut.log.info("Phone RAT 3G is enabled successfully.")
        return True

    def setup_2g(self):
        if self.dut_operator not in ("tmo", "uk_ee"):
            raise signals.TestSkip("2G is not supported, abort test.")
        if not phone_setup_voice_2g(self.log, self.dut):
            self.dut.log.error("Phone failed to setup 2G.")
            raise signals.TestFailure("Failed to setup 2G")
        self.dut.log.info("RAT 2G is enabled successfully.")
        return True

    def setup_vt(self):
        if CAPABILITY_VT not in self.dut_capabilities or (
                CAPABILITY_VT not in self.reference_capabilities):
            raise signals.TestSkip("VT is not supported, abort test.")
        for ad in (self.dut, self.ad_reference):
            if not phone_setup_video(self.log, ad):
                ad.log.error("Failed to setup VT.")
                return False
            return True

    def set_drop_reason_override(self, override_code=None):
        if not override_code:
            if self.dut.adb.shell("getprop vendor.radio.call_end_reason"):
                self.dut.adb.shell("setprop vendor.radio.call_end_reason ''")
        else:
            if self.dut.adb.shell("getprop vendor.radio.call_end_reason"
                                  ) != str(override_code):
                cmd = "setprop vendor.radio.call_end_reason %s" \
                      % override_code
                self.dut.log.info("====== %s ======", cmd)
                self.dut.adb.shell(cmd)

    def modem_crash(self):
        # Modem SSR
        self.user_params["check_crash"] = False
        self.dut.log.info("Triggering ModemSSR")
        try:
            self.dut.droid.logI("======== Trigger modem crash ========")
        except Exception:
            pass
        if (not self.dut.is_apk_installed("com.google.mdstest")
            ) or self.dut.adb.getprop("ro.build.version.release")[0] in (
                "8", "O", "7", "N") or self.dut.model in ("angler", "bullhead",
                                                          "sailfish",
                                                          "marlin"):
            trigger_modem_crash(self.dut)
        else:
            trigger_modem_crash_by_modem(self.dut)

    def call_drop_by_modem_crash(self,
                                 call_verification_function=None,
                                 vt=False):
        if vt:
            if not video_call_setup_teardown(
                    self.log,
                    self.dut,
                    self.ad_reference,
                    None,
                    video_state=VT_STATE_BIDIRECTIONAL,
                    verify_caller_func=is_phone_in_call_video_bidirectional,
                    verify_callee_func=is_phone_in_call_video_bidirectional):
                self.dut.log.error("VT Call Failed.")
                return False
        else:
            if not call_setup_teardown(
                    self.log,
                    self.dut,
                    self.ad_reference,
                    ad_hangup=None,
                    verify_caller_func=call_verification_function,
                    wait_time_in_call=10):
                self.log.error("Call setup failed")
                return False

        # Modem SSR
        self.modem_crash()

        try:
            if self.dut.droid.telecomIsInCall():
                self.dut.log.info("Still in call after trigger modem crash")
                return False
            else:
                reasons = self.dut.search_logcat(
                    "qcril_qmi_voice_map_qmi_to_ril_last_call_failure_cause")
                if reasons:
                    self.dut.log.info(reasons[-1]["log_message"])
        except Exception as e:
            self.dut.log.error(e)

    def toggle_apm(self):
        toggle_airplane_mode(self.log, self.dut, new_state=None)

    def toggle_wifi(self):
        wifi_toggle_state(self.log, self.dut, None)

    def drop_reason_override(self):
        hangup_call(self.log, self.ad_reference)

    def clearn_up_bugreport_database(self):
        self.dut.adb.shell(
            "rm /data/data/com.google.android.connectivitymonitor/"
            "shared_prefs/ConnectivityMonitor_BugReport.xml")

    def clearn_up_troubleshooter_database(self):
        self.dut.adb.shell(
            "rm /data/data/com.google.android.connectivitymonitor/"
            "shared_prefs/ConnectivityMonitor_TroubleshooterResult.xml")

    def parsing_bugreport_database(self):
        output = self.dut.adb.shell(
            "cat /data/data/com.google.android.connectivitymonitor/"
            "shared_prefs/ConnectivityMonitor_BugReport.xml")
        bugreport_database = re.findall(r">Call Drop:\s+(.*)<", output)
        self.dut.log.info("bugreport_database = %s", bugreport_database)
        return bugreport_database

    def parsing_troubleshooter_database(self):
        output = self.dut.adb.shell(
            "cat /data/data/com.google.android.connectivitymonitor/"
            "shared_prefs/ConnectivityMonitor_TroubleshooterResult.xml")
        results = re.findall(r"name=\"(\S+)\">(\S+)<", output)
        troubleshooter_database = {}
        for result in results:
            if "count" in result[0] or "num_calls" in result[0]:
                troubleshooter_database[result[0]] = int(result[1])
            else:
                troubleshooter_database[result[0]] = result[1]
        self.dut.log.info("TroubleshooterResult=%s",
                          sorted(troubleshooter_database.items()))
        return troubleshooter_database

    def parsing_call_summary(self):
        call_summary = self.dut.adb.shell(
            "dumpsys activity service com.google.android.connectivitymonitor/"
            ".ConnectivityMonitorService")
        self.dut.log.info(call_summary)
        call_summary_info = {}
        results = re.findall(
            r"(\S+): (\d+) out of (\d+) calls dropped, percentage=(\S+)",
            call_summary)
        for result in results:
            call_summary_info[result[0]] = int(result[2])
            call_summary_info["%s_dropped" % result[0]] = int(result[1])
            if result[3] == "NaN":
                call_summary_info["%s_dropped_percentage" % result[0]] = 0
            else:
                call_summary_info["%s_dropped_percentage" % result[0]] = float(
                    result[3])
        results = re.findall(r"(\S+): predominant failure reason=(.+)",
                             call_summary)
        for result in results:
            call_summary_info["%s_failure_reason" % result[0]] = result[1]
        self.dut.log.info("call summary dumpsys = %s",
                          sorted(call_summary_info.items()))
        return call_summary_info

    def parsing_call_statistics(self):
        call_statistics_info = {}
        call_statistics = self.dut.adb.shell(
            "content query --uri content://com.google.android."
            "connectivitymonitor.troubleshooterprovider/call_statistics")
        self.dut.log.info("troubleshooterprovider call_statistics:\n%s",
                          call_statistics)
        results = re.findall(r"KEY=(\S+), VALUE=(\S+)", call_statistics)
        for result in results:
            if ("count" in result[0] or "num_calls" in result[0]):
                if result[1] == "NULL":
                    call_statistics_info[result[0]] = 0
                else:
                    call_statistics_info[result[0]] = int(result[1])
            else:
                call_statistics_info[result[0]] = result[1]
        self.dut.log.info("troubleshooterprovider call_statistics: %s",
                          sorted(call_statistics_info.items()))
        return call_statistics_info

    def parsing_diagnostics(self):
        diagnostics_info = {}
        diagnostics = self.dut.adb.shell(
            "content query --uri content://com.google.android."
            "connectivitymonitor.troubleshooterprovider/diagnostics")
        self.dut.log.info("troubleshooterprovider diagnostics:\n%s",
                          diagnostics)
        results = re.findall(r"KEY=(\S+), VALUE=(\S+)", diagnostics)
        for result in results:
            diagnostics_info[result[0]] = result[1]
        self.dut.log.info("troubleshooterprovider diagnostics: %s",
                          sorted(diagnostics_info.items()))
        return diagnostics_info

    def call_setup_and_connectivity_monitor_checking(self,
                                                     setup=None,
                                                     handover=None,
                                                     triggers=[],
                                                     expected_drop_reason="",
                                                     expected_trouble=None,
                                                     expected_action=None):

        call_verification_function = None
        begin_time = get_device_epoch_time(self.dut)
        call_data_summary_before = self.parsing_call_summary()
        call_statistics_before = self.parsing_call_statistics()
        self.parsing_diagnostics()
        self.parsing_troubleshooter_database()
        bugreport_database_before = self.parsing_bugreport_database()

        if expected_drop_reason:
            expected_drop_reasons = set(expected_drop_reason.split("|"))
        else:
            expected_drop_reasons = set()
        checking_counters = ["Calls"]
        checking_reasons = []
        result = True
        if setup in ("wfc_apm", "wfc_non_apm"):
            call_verification_function = is_phone_in_call_iwlan
        elif setup == "volte":
            call_verification_function = is_phone_in_call_volte
        elif setup == "csfb":
            call_verification_function = is_phone_in_call_csfb
        elif setup == "3g":
            call_verification_function = is_phone_in_call_3g
        elif setup == "2g":
            call_verification_function = is_phone_in_call_2g
        technology = handover or setup
        if technology in ("wfc_apm", "wfc_non_apm"):
            if triggers and triggers[0] not in IGNORED_CALL_DROP_TRIGGERS:
                checking_counters.extend(
                    ["Calls_dropped", "VOWIFI", "VOWIFI_dropped"])
                checking_reasons.append("VOWIFI_failure_reason")
            elif call_data_summary_before.get("Calls_dropped", 0):
                checking_counters.append("VOWIFI")
        elif technology == "volte":
            if triggers and triggers[0] not in IGNORED_CALL_DROP_TRIGGERS:
                checking_counters.extend(
                    ["Calls_dropped", "VOLTE", "VOLTE_dropped"])
                checking_reasons.append("VOLTE_failure_reason")
            elif call_data_summary_before.get("Calls_dropped", 0):
                checking_counters.append("VOLTE")
        elif technology in ("csfb", "3g", "2g"):
            if triggers and triggers[0] not in IGNORED_CALL_DROP_TRIGGERS:
                checking_counters.extend(["Calls_dropped", "CS", "CS_dropped"])
                checking_reasons.append("CS_failure_reason")
            elif call_data_summary_before.get("Calls_dropped", 0):
                checking_counters.append("CS")

        if setup == "vt":
            if not video_call_setup_teardown(
                    self.log,
                    self.dut,
                    self.ad_reference,
                    None,
                    video_state=VT_STATE_BIDIRECTIONAL,
                    verify_caller_func=is_phone_in_call_video_bidirectional,
                    verify_callee_func=is_phone_in_call_video_bidirectional):
                raise signals.TestFailure("VT Call Failed.")
        else:
            if not call_setup_teardown(
                    self.log,
                    self.dut,
                    self.ad_reference,
                    ad_hangup=None,
                    verify_caller_func=call_verification_function,
                    wait_time_in_call=10):
                raise signals.TestFailure("Call Setup Failed.")

        for trigger in triggers:
            if self.dut.droid.telecomIsInCall():
                self.dut.log.info("Telecom is in call")
                self.dut.log.info(
                    "Voice in RAT %s",
                    self.dut.droid.telephonyGetCurrentVoiceNetworkType())
            else:
                self.dut.log.info("Not in call")
            # Trigger in-call event
            if trigger and getattr(self, trigger, None):
                trigger_func = getattr(self, trigger)
                trigger_func()
                time.sleep(MAX_WAIT_TIME_FOR_STATE_CHANGE)

        if self.dut.droid.telecomIsInCall():
            self.dut.log.info("Telecom is in call")
            self.dut.log.info(
                "Voice in RAT %s",
                self.dut.droid.telephonyGetCurrentVoiceNetworkType())
        else:
            self.dut.log.info("Not in call")

        if self.dut.droid.telecomIsInCall():
            self.dut.log.info("Telecom is in call")
            self.dut.log.info(
                "Voice in RAT %s",
                self.dut.droid.telephonyGetCurrentVoiceNetworkType())
        else:
            self.dut.log.info("Not in call")

        drop_reason = last_call_drop_reason(self.dut, begin_time)
        drop_reason = drop_reason.title()
        if drop_reason:
            expected_drop_reasons.add(drop_reason)
        for ad in (self.ad_reference, self.dut):
            try:
                if ad.droid.telecomIsInCall():
                    if triggers:
                        ad.log.info("Still in call after triggers %s",
                                    triggers)
                        result = False
                    hangup_call(self.log, ad)
                    time.sleep(MAX_WAIT_TIME_FOR_STATE_CHANGE)
            except Exception as e:
                ad.log.error(e)

        call_data_summary_after = self.parsing_call_summary()
        call_statistics_after = self.parsing_call_statistics()
        diagnostics_after = self.parsing_diagnostics()
        ts_database_after = self.parsing_troubleshooter_database()

        for counter in checking_counters:
            if call_data_summary_after.get(
                    counter,
                    0) != call_data_summary_before.get(counter, 0) + 1:
                self.dut.log.error("Counter %s did not increase", counter)
                result = False
            else:
                self.dut.log.info("Counter %s increased", counter)
            if counter == "Calls":
                if call_statistics_after.get("num_calls",
                                             0) - call_statistics_before.get(
                                                 "num_calls", 0) < 1:
                    self.dut.log.warning(
                        "call_statistics num_calls didn't increase")
                    # result = False
                else:
                    self.dut.log.info("call_statistics num_calls increased")
            if "_dropped" in counter and counter != "Calls_dropped":
                desc = counter.split("_")[0]
                if desc == "VOWIFI":
                    stat_key = "recent_wfc_fail_count"
                else:
                    stat_key = "recent_%s_fail_count" % desc.lower()
                before = call_statistics_after.get(stat_key, 0)
                after = call_statistics_after.get(stat_key, 0)
                most_failure_call_type = call_statistics_after.get(
                    "call_type_with_most_failures")
                diagnosis = diagnostics_after.get("diagnosis")
                actions = diagnostics_after.get("actions")
                if after - before < 1:
                    self.dut.log.warning("call_statistics %s didn't increase, "
                                         "before %s, after %s" %
                                         (stat_key, before, after))
                    # result = False
                else:
                    self.dut.log.info("call_statistics %s increased", stat_key)
                if most_failure_call_type != desc:
                    self.dut.log.warning(
                        "call_statistics call_type_with_most_failures "
                        "is %s, not %s", most_failure_call_type, desc)
                else:
                    self.dut.log.info(
                        "call_statistics call_type_with_most_failures is %s",
                        most_failure_call_type)
                dropped = call_data_summary_after.get("%s_dropped" % desc, 0)
                drop_percentage = call_data_summary_after.get(
                    "%s_dropped_percentage" % desc, 0)
                self.dut.log.info("%s_dropped = %s, percentage = %s", desc,
                                  dropped, drop_percentage)
                if expected_trouble and expected_trouble != diagnosis:
                    self.dut.log.warning("diagnoisis = %s, expecting %s",
                                         diagnosis, expected_trouble)
                if expected_action and expected_action != actions:
                    self.dut.log.error("actions = %s, expecting %s", actions,
                                       expected_action)
                    result = False
                if drop_percentage > CALL_TROUBLE_THRESHOLD and (
                        dropped > CONSECUTIVE_CALL_FAILS):
                    if diagnosis == "UNABLE_TO_TRIAGE":
                        self.dut.log.error(
                            "troubleshooter diagnosis is %s with %s dropped "
                            "and %s drop_percentage", diagnosis, dropped,
                            drop_percentage)
                        result = False
                    if actions == "NONE":
                        self.dut.log.error(
                            "troubleshooter failed to provide suggestion, "
                            "actions = %s", actions)
                        result = False
        if expected_drop_reasons:
            expected_drop_reason = "|".join(expected_drop_reasons)
        for reason_key in checking_reasons:
            if call_data_summary_after.get(reason_key, None):
                drop_reason = call_data_summary_after[reason_key]
                if expected_drop_reason and drop_reason not in expected_drop_reason:
                    self.dut.log.error("%s is: %s, expecting %s", reason_key,
                                       drop_reason, expected_drop_reason)
                    result = False
                else:
                    self.dut.log.info("%s is: %s as expected", reason_key,
                                      drop_reason)
            else:
                self.dut.log.error("%s is not provided in summary report",
                                   reason_key)
                result = False

        if not triggers or triggers[0] in IGNORED_CALL_DROP_TRIGGERS:
            return result
        if drop_reason in bugreport_database_before:
            self.dut.log.info("%s is in bugreport database %s before call",
                              drop_reason, bugreport_database_before)
            return result
        else:
            self.dut.log.info("%s is not in bugreport database %s before call",
                              drop_reason, bugreport_database_before)
        if drop_reason in IGNORED_CALL_DROP_REASONS:
            self.dut.log.info(
                "Call drop with reason %s will skip bugreport notification",
                drop_reason)
            return result
        else:
            self.dut.log.info(
                "Call drop %s should generate bugreport notification",
                drop_reason)
        # Parse logcat for UI notification only for the first failure
        if self.dut.search_logcat("Bugreport notification title Call Drop:",
                                  begin_time):
            self.dut.log.info(
                "Bugreport notification title Call Drop is seen in logcat")
            return result
        else:
            self.dut.log.error(
                "Bugreport notification title Call Drop is not seen in logcat")
            return False

    def call_drop_test(self,
                       setup=None,
                       handover=None,
                       count=CONSECUTIVE_CALL_FAILS,
                       triggers=[],
                       expected_drop_reason=None,
                       expected_trouble=None,
                       expected_action=None):
        if not triggers:
            if self.dut.model in ("marlin", "sailfish", "walleye", "taimen"):
                triggers = ["modem_crash"]
                expected_drop_reason = "Error Unspecified"
            else:
                triggers = ["drop_reason_override"]
        if "drop_reason_override" in triggers:
            self.set_drop_reason_override(
                override_code=self.call_drop_override_code)
            expected_drop_reason = CALL_DROP_CODE_MAPPING[int(
                self.call_drop_override_code)]
        for iter in range(count):
            self.dut.log.info("===== %s_iter_%s =====", self.test_name,
                              iter + 1)
            if iter < count - 1:
                action = None
                trouble = None
            else:
                action = expected_action
                trouble = expected_trouble
            if not self.call_setup_and_connectivity_monitor_checking(
                    setup=setup,
                    handover=handover,
                    triggers=triggers,
                    expected_drop_reason=expected_drop_reason,
                    expected_trouble=trouble,
                    expected_action=action):
                return False
        return True

    def call_drop_triggered_suggestion_test(self,
                                            setup=None,
                                            handover=None,
                                            triggers=[],
                                            expected_drop_reason=None,
                                            expected_trouble=None,
                                            expected_action=None):
        call_summary = self.parsing_call_summary()
        diagnostics = self.parsing_diagnostics()
        diagnosis = diagnostics.get("diagnosis")
        actions = diagnostics.get("actions")
        self.dut.log.info("Expected trouble = %s, action = %s",
                          expected_trouble, expected_action)
        if expected_trouble and diagnosis == expected_trouble and not handover:
            self.dut.log.info("Diagnosis is the expected %s", trouble)
            if expected_action and expected_action != actions:
                self.dut.log.error("Action is %s, expecting %s", actions,
                                   expected_action)
                result = False
            if setup in ("wfc_apm", "wfc_non_apm"):
                desc = "VOWIFI"
            elif setup == "volte":
                desc = "VOLTE"
            elif setup in ("csfb", "3g", "2g"):
                desc = "CS"
            drops = call_summary.get("%s_dropped" % desc, 0)
            drop_percentage = call_summary.get("%s_dropped_percentage" % desc,
                                               0)
            if drops < CONSECUTIVE_CALL_FAILS or drop_percentage < 25:
                self.dut.log.error(
                    "Should NOT get %s for %s %s_dropped and %s %s_dropped_percentage",
                    trouble, drops, desc, drop_percentage, desc)
                return False
            else:
                return True
        else:
            self.dut.log.info("Generate %s consecutive call drops",
                              CONSECUTIVE_CALL_FAILS)
            return self.call_drop_test(
                setup=setup,
                handover=handover,
                count=CONSECUTIVE_CALL_FAILS,
                triggers=triggers,
                expected_drop_reason=expected_drop_reason,
                expected_trouble=expected_trouble,
                expected_action=expected_action)

    def healthy_call_test(self,
                          setup=None,
                          handover=None,
                          count=1,
                          triggers=[],
                          expected_trouble=None,
                          expected_action=None):
        if self.dut.model not in ("marlin", "sailfish", "walleye", "taimen"):
            self.set_drop_reason_override(override_code=25)
        result = True
        for iter in range(count):
            if not self.call_setup_and_connectivity_monitor_checking(
                    setup=setup,
                    handover=handover,
                    triggers=triggers,
                    expected_trouble=expected_trouble,
                    expected_action=expected_action):
                return False
        return True

    def forced_call_drop_test(self,
                              setup=None,
                              handover=None,
                              triggers=None,
                              expected_drop_reason=None):
        expected_trouble = None
        expected_action = None
        technology = handover or setup
        if setup:
            setup_func = getattr(self, "setup_%s" % setup)
            if not setup_func(): return False
            if technology == "volte":
                expected_trouble = TROUBLES[6],
                expected_action = ACTIONS[6]
            elif technology == "csfb":
                if CAPABILITY_VOLTE in self.dut_capabilities:
                    expected_action = ACTIONS[5]
                else:
                    expected_action = ACTIONS[7]
                expected_trouble = TROUBLES[7]
            elif technology == "3g":
                if CAPABILITY_VOLTE in self.dut_capabilities:
                    expected_action = ACTIONS[5]
                else:
                    expected_action = ACTIONS[7]
                expected_trouble = TROUBLES[7]
            elif technology == "2g":
                if CAPABILITY_VOLTE in self.dut_capabilities:
                    expected_action = ACTIONS[5]
                else:
                    expected_action = ACTIONS[7]
                expected_trouble = TROUBLES[7]
            elif technology == "wfc_apm":
                expected_trouble = TROUBLES[3]
                expected_action = ACTIONS[11]
            elif technology == "wfc_non_apm":
                expected_trouble = TROUBLES[3]
                expected_action = ACTIONS[3]

        return self.call_drop_triggered_suggestion_test(
            setup=setup,
            handover=handover,
            triggers=triggers,
            expected_drop_reason=expected_drop_reason,
            expected_trouble=expected_trouble,
            expected_action=expected_action)

    def call_drop_test_after_wipe(self, setup=None):
        if setup:
            setup_func = getattr(self, "setup_%s" % setup)
            if not setup_func(): return False
        fastboot_wipe(self.dut)
        bring_up_connectivity_monitor(self.dut)
        return self.forced_call_drop_test(setup=setup)

    def call_drop_test_after_reboot(self, setup=None):
        self.forced_call_drop_test(setup=setup)
        self.healthy_call_test(setup=setup, count=1)
        reboot_device(self.dut)
        return self.forced_call_drop_test(setup=setup)
