• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
3#   Copyright 2022 - 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
17import time
18from datetime import datetime
19
20from acts import signals
21from acts.libs.proc import job
22from acts.libs.utils.multithread import multithread_func
23from acts.test_decorators import test_tracker_info
24from acts_contrib.test_utils.tel.loggers.protos.telephony_metric_pb2 import TelephonyVoiceTestResult
25from acts_contrib.test_utils.tel.loggers.telephony_metric_logger import TelephonyMetricLogger
26from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
27from acts_contrib.test_utils.tel.tel_defines import INVALID_SUB_ID
28from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
29from acts_contrib.test_utils.tel.tel_defines import WFC_MODE_CELLULAR_PREFERRED
30from acts_contrib.test_utils.tel.tel_data_utils import reboot_test
31from acts_contrib.test_utils.tel.tel_ims_utils import toggle_wfc_for_subscription
32from acts_contrib.test_utils.tel.tel_ims_utils import set_wfc_mode_for_subscription
33from acts_contrib.test_utils.tel.tel_ims_utils import wait_for_wfc_enabled
34from acts_contrib.test_utils.tel.tel_logging_utils import start_pixellogger_always_on_logging
35from acts_contrib.test_utils.tel.tel_parse_utils import check_ims_cst_reg
36from acts_contrib.test_utils.tel.tel_parse_utils import parse_cst_reg
37from acts_contrib.test_utils.tel.tel_parse_utils import print_nested_dict
38from acts_contrib.test_utils.tel.tel_phone_setup_utils import ensure_phones_idle
39from acts_contrib.test_utils.tel.tel_phone_setup_utils import phone_setup_on_rat
40from acts_contrib.test_utils.tel.tel_subscription_utils import get_incoming_voice_sub_id
41from acts_contrib.test_utils.tel.tel_subscription_utils import get_outgoing_voice_sub_id
42from acts_contrib.test_utils.tel.tel_subscription_utils import get_slot_index_from_subid
43from acts_contrib.test_utils.tel.tel_subscription_utils import get_subid_from_slot_index
44from acts_contrib.test_utils.tel.tel_subscription_utils import set_voice_sub_id
45from acts_contrib.test_utils.tel.tel_subscription_utils import set_dds_on_slot
46from acts_contrib.test_utils.tel.tel_subscription_utils import get_subid_on_same_network_of_host_ad
47from acts_contrib.test_utils.tel.tel_test_utils import verify_http_connection
48from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_on_rat
49from acts_contrib.test_utils.tel.tel_voice_utils import two_phone_call_msim_for_slot
50from acts.utils import set_location_service
51from acts.utils import get_current_epoch_time
52
53WAIT_FOR_CST_REG_TIMEOUT = 120
54CALCULATE_EVERY_N_CYCLES = 10
55
56CallResult = TelephonyVoiceTestResult.CallResult.Value
57
58
59class TelLiveRilCstVoiceTest(TelephonyBaseTest):
60    def setup_class(self):
61        TelephonyBaseTest.setup_class(self)
62        start_pixellogger_always_on_logging(self.android_devices[0])
63        self.tel_logger = TelephonyMetricLogger.for_test_case()
64
65
66    def teardown_test(self):
67        self.enable_cst(self.android_devices[0], None, False)
68        self.force_roaming(self.android_devices[0], None, 0)
69        ensure_phones_idle(self.log, self.android_devices)
70
71
72    def enable_cst(self, ad, slot=0, enable=True):
73        """Enable/disable Cross SIM Calling by SL4A API at given slot
74
75            Args:
76                ad: Android object
77                slot: 0 for pSIM and 1 for eSIM
78                enable: True fo enabling and False for disabling
79
80            Raises:
81                TestFailure if False is returned by
82                imsMmTelIsCrossSimCallingEnabled.
83        """
84        if slot is None:
85            slots = [0, 1]
86        else:
87            slots = [slot]
88
89        for slot in slots:
90            sub_id = get_subid_from_slot_index(self.log, ad, slot)
91            if ad.droid.imsMmTelIsCrossSimCallingEnabled(sub_id) == enable:
92                ad.log.info(
93                    'Status of backup calling at slot %s is already %s.',
94                    slot, enable)
95            else:
96                ad.droid.imsMmTelSetCrossSimCallingEnabled(sub_id, enable)
97                time.sleep(3)
98                if ad.droid.imsMmTelIsCrossSimCallingEnabled(sub_id) == enable:
99                    ad.log.info(
100                        'Backup calling at slot %s is set to %s successfully.',
101                        slot, enable)
102                else:
103                    ad.log.error(
104                        'Backup calling at slot %s is NOT set to %s.',
105                        slot, enable)
106                    raise signals.TestFailure(
107                        "Failed",
108                        extras={"fail_reason": "Failed to set Backup calling."})
109
110
111    def get_force_roaming_state(self, ad, slot=0):
112        """Get the value of the property:
113                getprop persist.vendor.radio.force_roaming
114
115            Args:
116                ad: Android object
117                slot: 0 for pSIM and 1 for eSIM
118
119            Returns:
120                0 for not roaming and 1 for roaming
121        """
122        cmd = 'adb -s %s shell getprop persist.vendor.radio.force_roaming%s' % (
123            ad.serial, slot)
124        result = job.run(cmd)
125        return result.stdout
126
127
128    def force_roaming(self, ad, slot=0, roaming=0):
129        """Force assigned slot to roam ot not to roam by setting specific property
130
131            Args:
132                ad: Android object
133                slot: 0 for pSIM and 1 for eSIM
134                roaming: 1 to force to roam. Otherwise 0.
135
136            Returns:
137                True or False
138        """
139        if slot is None:
140            slots = [0, 1]
141        else:
142            slots = [slot]
143
144        need_reboot = 0
145        for slot in slots:
146            roamimg_state = self.get_force_roaming_state(ad, slot)
147            if roamimg_state:
148                if roamimg_state == str(roaming):
149                    if roaming:
150                        ad.log.info('Slot %s is already roaming.' % slot)
151                    else:
152                        ad.log.info('Slot %s is already on home network.' % slot)
153                else:
154                    cmd = 'adb -s %s shell setprop persist.vendor.radio.force_roaming%s %s' % (ad.serial, slot, roaming)
155                    result = job.run(cmd)
156                    self.log.info(result)
157                    need_reboot = 1
158
159        if not need_reboot:
160            return True
161        else:
162            result = True
163            if reboot_test(self.log, ad):
164                for slot in slots:
165                    roamimg_state = self.get_force_roaming_state(ad, slot)
166                    if roamimg_state == str(roaming):
167                        if roaming:
168                            ad.log.info('Slot %s is now roaming.' % slot)
169                        else:
170                            ad.log.info('Slot %s is now on home network.' % slot)
171                    else:
172                        if roaming:
173                            ad.log.error(
174                                'Slot %s is expected to be roaming (roamimg state: %s).' % roamimg_state)
175                        else:
176                            ad.log.error(
177                                'Slot %s is expected to be on home network (roamimg state: %s).' % roamimg_state)
178                        result = False
179            return result
180
181
182    def msim_cst_registration(
183            self,
184            cst_slot,
185            rat=["", ""],
186            wfc_mode=WFC_MODE_WIFI_PREFERRED,
187            force_roaming=False,
188            test_cycles=1):
189        """Make MO/MT voice call at specific slot in specific RAT with DDS at
190        specific slot.
191
192        Test step:
193        1. Get sub IDs of specific slots of both MO and MT devices.
194        2. Switch DDS to specific slot.
195        3. Check HTTP connection after DDS switch.
196        4. Set up phones in desired RAT.
197
198        Args:
199            cst_slot: Slot at which CST registered
200            rat: RAT for both slots
201            wfc_mode: cullelar-preferred or wifi-preferred
202            force_roaming: True for fake roaming by setprop
203            test_cycles: Amount of the test cycles
204
205        Returns:
206            True in the end. Otherwise the exception TestFailure will be raised.
207
208        Raises:
209            TestFailure if:
210                1. Invalid sub ID is returned.
211                2. DDS cannot be switched successfully.
212                3. Http connection cannot be verified.
213        """
214        ads = self.android_devices
215        set_location_service(ads[0], True)
216        test_cycles = int(test_cycles)
217        cst_reg_search_intervals = []
218        cst_reg_fail = 0
219        exit_due_to_high_fail_rate = False
220        for attempt in range(test_cycles):
221            self.log.info(
222                '======> Test cycle %s/%s <======', attempt + 1, test_cycles)
223            cst_reg_begin_time = datetime.now()
224            cst_slot_sub_id = get_subid_from_slot_index(
225                self.log, ads[0], cst_slot)
226            if cst_slot_sub_id == INVALID_SUB_ID:
227                ads[0].log.warning(
228                    "Failed to get sub ID ar slot %s.", cst_slot)
229                raise signals.TestFailure(
230                    'Failed',
231                    extras={
232                        'fail_reason': 'Slot ID %s at slot %s is invalid.' % (
233                            cst_slot_sub_id, cst_slot)})
234            other_sub_id = get_subid_from_slot_index(
235                self.log, ads[0], 1-cst_slot)
236            self.enable_cst(ads[0], slot=cst_slot, enable=True)
237
238            self.log.info("Step 1: Switch DDS.")
239            if not set_dds_on_slot(ads[0], 1 - cst_slot):
240                ads[0].log.warning("Failed to set DDS to slot %s.", 1 - cst_slot)
241                raise signals.TestFailure(
242                    'Failed',
243                    extras={
244                        'fail_reason': 'Failed to set DDS to slot %s.' % 1 - cst_slot})
245
246            self.log.info("Step 2: Check HTTP connection after DDS switch.")
247            if not verify_http_connection(self.log,
248                ads[0],
249                url="https://www.google.com",
250                retry=5,
251                retry_interval=15,
252                expected_state=True):
253                self.log.error("Failed to verify http connection.")
254                raise signals.TestFailure(
255                    'Failed',
256                    extras={
257                        'fail_reason': 'Failed to verify http connection.'})
258            else:
259                self.log.info("Verify http connection successfully.")
260
261            self.log.info("Step 3: Set up phones in desired RAT.")
262            phone_setup_on_rat(
263                self.log,
264                ads[0],
265                rat[1-cst_slot],
266                other_sub_id)
267
268            phone_setup_on_rat(
269                self.log,
270                ads[0],
271                rat[cst_slot],
272                cst_slot_sub_id,
273                False,
274                wfc_mode)
275
276            if toggle_wfc_for_subscription(
277                self.log, ads[0], True, cst_slot_sub_id):
278                if set_wfc_mode_for_subscription(
279                    ads[0], wfc_mode, cst_slot_sub_id):
280                    pass
281
282            if force_roaming:
283                self.force_roaming(ads[0], cst_slot, 1)
284
285            if not wait_for_wfc_enabled(self.log, ads[0]):
286                cst_reg_fail += 1
287                if cst_reg_fail >= test_cycles/10:
288                    exit_due_to_high_fail_rate = True
289
290            cst_reg_end_time = datetime.now()
291            ims_cst_reg_res = check_ims_cst_reg(
292                ads[0],
293                cst_slot,
294                search_interval=[cst_reg_begin_time, cst_reg_end_time])
295
296            while not ims_cst_reg_res:
297                if (datetime.now() - cst_reg_end_time).total_seconds() > WAIT_FOR_CST_REG_TIMEOUT:
298                    break
299                time.sleep(1)
300                ims_cst_reg_res = check_ims_cst_reg(
301                    ads[0],
302                    cst_slot,
303                    search_interval=[cst_reg_begin_time, datetime.now()])
304
305            if not ims_cst_reg_res:
306                ads[0].log.error('IMS radio tech is NOT CrossStackEpdg.')
307                cst_reg_fail += 1
308                if cst_reg_fail >= test_cycles/10:
309                    exit_due_to_high_fail_rate = True
310
311            if (attempt+1) % CALCULATE_EVERY_N_CYCLES == 0 or (
312                attempt == test_cycles - 1) or exit_due_to_high_fail_rate:
313
314                parsing_fail = parse_cst_reg(
315                    ads[0], cst_slot, cst_reg_search_intervals)
316                ads[0].log.info('====== Failed cycles of CST registration ======')
317                for each_dict in parsing_fail:
318                    print_nested_dict(ads[0], each_dict)
319
320            self.enable_cst(ads[0], None, False)
321
322            if exit_due_to_high_fail_rate:
323                ads[0].log.error(
324                    'Test case is stopped due to fail rate is greater than 10%.')
325                break
326
327        return True
328
329
330    def msim_cst_call_voice(
331            self,
332            mo_slot,
333            mt_slot,
334            mo_rat=["", ""],
335            mt_rat=["", ""],
336            call_direction="mo",
337            wfc_mode=WFC_MODE_WIFI_PREFERRED,
338            force_roaming=False,
339            test_cycles=1,
340            call_cycles=1):
341        """Make MO/MT voice call at specific slot in specific RAT with DDS at
342        specific slot.
343
344        Test step:
345        1. Get sub IDs of specific slots of both MO and MT devices.
346        2. Switch DDS to specific slot.
347        3. Check HTTP connection after DDS switch.
348        4. Set up phones in desired RAT.
349        5. Make voice call.
350
351        Args:
352            mo_slot: Slot making MO call (0 or 1)
353            mt_slot: Slot receiving MT call (0 or 1)
354            mo_rat: RAT for both slots of MO device
355            mt_rat: RAT for both slots of MT device
356            call_direction: "mo" or "mt"
357
358        Returns:
359            True in the end. Otherwise the exception TestFailure will be raised.
360
361        Raises:
362            TestFailure if:
363                1. Invalid sub ID is returned.
364                2. DDS cannot be switched successfully.
365                3. Http connection cannot be verified.
366        """
367        ads = self.android_devices
368        if call_direction == "mo":
369            ad_mo = ads[0]
370            ad_mt = ads[1]
371        else:
372            ad_mo = ads[1]
373            ad_mt = ads[0]
374
375        test_cycles = int(test_cycles)
376        call_cycles = int(call_cycles)
377        set_location_service(ads[0], True)
378        cst_reg_search_intervals = []
379        cst_reg_fail = 0
380        exit_due_to_high_fail_rate = False
381        dialed_call_amount = 0
382        call_result_list = []
383        call_result_cycle_list = []
384        for attempt in range(test_cycles):
385            self.log.info(
386                '======> Test cycle %s/%s <======', attempt + 1, test_cycles)
387            cst_reg_begin_time = datetime.now()
388            if mo_slot is not None:
389                mo_sub_id = get_subid_from_slot_index(self.log, ad_mo, mo_slot)
390                if mo_sub_id == INVALID_SUB_ID:
391                    ad_mo.log.warning(
392                        "Failed to get sub ID ar slot %s.", mo_slot)
393                    raise signals.TestFailure(
394                        'Failed',
395                        extras={
396                            'fail_reason': 'Slot ID %s at slot %s is invalid.' % (
397                                mo_sub_id, mo_slot)})
398                mo_other_sub_id = get_subid_from_slot_index(
399                    self.log, ad_mo, 1-mo_slot)
400                set_voice_sub_id(ad_mo, mo_sub_id)
401                self.enable_cst(ads[0], slot=mo_slot, enable=True)
402            else:
403                _, mo_sub_id, _ = get_subid_on_same_network_of_host_ad(ads)
404                if mo_sub_id == INVALID_SUB_ID:
405                    ad_mo.log.warning(
406                        "Failed to get sub ID ar slot %s.", mo_slot)
407                    raise signals.TestFailure(
408                        'Failed',
409                        extras={
410                            'fail_reason': 'Slot ID %s at slot %s is invalid.' % (
411                                mo_sub_id, mo_slot)})
412                mo_slot = "auto"
413                set_voice_sub_id(ad_mo, mo_sub_id)
414            ad_mo.log.info("Sub ID for outgoing call at slot %s: %s",
415                mo_slot, get_outgoing_voice_sub_id(ad_mo))
416
417            if mt_slot is not None and mt_slot is not 'auto':
418                mt_sub_id = get_subid_from_slot_index(
419                    self.log, ad_mt, mt_slot)
420                if mt_sub_id == INVALID_SUB_ID:
421                    ad_mt.log.warning(
422                        "Failed to get sub ID at slot %s.", mt_slot)
423                    raise signals.TestFailure(
424                        'Failed',
425                        extras={
426                            'fail_reason': 'Slot ID %s at slot %s is invalid.' % (
427                                mt_sub_id, mt_slot)})
428                mt_other_sub_id = get_subid_from_slot_index(
429                    self.log, ad_mt, 1-mt_slot)
430                set_voice_sub_id(ad_mt, mt_sub_id)
431                self.enable_cst(ads[0], slot=mt_slot, enable=True)
432            else:
433                _, mt_sub_id, _ = get_subid_on_same_network_of_host_ad(ads)
434                if mt_sub_id == INVALID_SUB_ID:
435                    ad_mt.log.warning(
436                        "Failed to get sub ID at slot %s.", mt_slot)
437                    raise signals.TestFailure(
438                        'Failed',
439                        extras={
440                            'fail_reason': 'Slot ID %s at slot %s is invalid.' % (
441                                mt_sub_id, mt_slot)})
442                mt_slot = "auto"
443                set_voice_sub_id(ad_mt, mt_sub_id)
444            ad_mt.log.info("Sub ID for incoming call at slot %s: %s", mt_slot,
445                get_incoming_voice_sub_id(ad_mt))
446
447            self.log.info("Step 1: Switch DDS.")
448
449            dds_slot = 1
450            if call_direction == "mo":
451                dds_slot = 1 - get_slot_index_from_subid(ad_mo, mo_sub_id)
452            else:
453                dds_slot = 1 - get_slot_index_from_subid(ad_mt, mt_sub_id)
454
455            if not set_dds_on_slot(ads[0], dds_slot):
456                ads[0].log.warning("Failed to set DDS to slot %s.", dds_slot)
457                raise signals.TestFailure(
458                    'Failed',
459                    extras={
460                        'fail_reason': 'Failed to set DDS to slot %s.' % dds_slot})
461
462            self.log.info("Step 2: Check HTTP connection after DDS switch.")
463            if not verify_http_connection(self.log,
464                ads[0],
465                url="https://www.google.com",
466                retry=5,
467                retry_interval=15,
468                expected_state=True):
469                self.log.error("Failed to verify http connection.")
470                raise signals.TestFailure(
471                    'Failed',
472                    extras={
473                        'fail_reason': 'Failed to verify http connection.'})
474            else:
475                self.log.info("Verify http connection successfully.")
476
477            if mo_slot == 0 or mo_slot == 1:
478                phone_setup_on_rat(
479                    self.log,
480                    ad_mo,
481                    mo_rat[1-mo_slot],
482                    mo_other_sub_id)
483
484                mo_phone_setup_argv = (
485                self.log,
486                ad_mo,
487                mo_rat[mo_slot],
488                mo_sub_id,
489                False,
490                wfc_mode)
491
492                is_mo_in_call = is_phone_in_call_on_rat(
493                    self.log, ad_mo, mo_rat[mo_slot], only_return_fn=True)
494            else:
495                mo_phone_setup_argv = (self.log, ad_mo, 'general')
496                is_mo_in_call = is_phone_in_call_on_rat(
497                    self.log, ad_mo, 'general', only_return_fn=True)
498
499            if mt_slot == 0 or mt_slot == 1:
500                phone_setup_on_rat(
501                    self.log,
502                    ad_mt,
503                    mt_rat[1-mt_slot],
504                    mt_other_sub_id)
505
506                mt_phone_setup_argv = (
507                self.log,
508                ad_mt,
509                mt_rat[mt_slot],
510                mt_sub_id,
511                False,
512                wfc_mode)
513
514                is_mt_in_call = is_phone_in_call_on_rat(
515                    self.log, ad_mt, mt_rat[mt_slot], only_return_fn=True)
516            else:
517                mt_phone_setup_argv = (self.log, ad_mt, 'general')
518                is_mt_in_call = is_phone_in_call_on_rat(
519                    self.log, ad_mt, 'general', only_return_fn=True)
520
521            self.log.info("Step 3: Set up phones in desired RAT.")
522
523            tasks = [(phone_setup_on_rat, mo_phone_setup_argv),
524                    (phone_setup_on_rat, mt_phone_setup_argv)]
525            if not multithread_func(self.log, tasks):
526                self.log.error("Phone Failed to Set Up Properly.")
527                self.tel_logger.set_result(CallResult("CALL_SETUP_FAILURE"))
528                raise signals.TestFailure("Failed",
529                    extras={"fail_reason": "Phone Failed to Set Up Properly."})
530
531            if toggle_wfc_for_subscription(self.log, ad_mo, True, mo_sub_id):
532                if set_wfc_mode_for_subscription(ad_mo, wfc_mode, mo_sub_id):
533                    pass
534
535            if force_roaming:
536                self.force_roaming(ads[0], 1-dds_slot, 1)
537
538            if not wait_for_wfc_enabled(self.log, ads[0]):
539                cst_reg_fail += 1
540                if cst_reg_fail >= test_cycles/10:
541                    exit_due_to_high_fail_rate = True
542
543            cst_reg_end_time = datetime.now()
544            ims_cst_reg_res = check_ims_cst_reg(
545                ads[0],
546                1-dds_slot,
547                search_interval=[cst_reg_begin_time, cst_reg_end_time])
548
549            while not ims_cst_reg_res:
550                if (datetime.now() - cst_reg_end_time).total_seconds() > WAIT_FOR_CST_REG_TIMEOUT:
551                    break
552                time.sleep(1)
553                ims_cst_reg_res = check_ims_cst_reg(
554                    ads[0],
555                    1-dds_slot,
556                    search_interval=[cst_reg_begin_time, datetime.now()])
557
558            if not ims_cst_reg_res:
559                ads[0].log.error('IMS radio tech is NOT CrossStackEpdg.')
560                cst_reg_fail += 1
561                if cst_reg_fail >= test_cycles/10:
562                    exit_due_to_high_fail_rate = True
563
564            if ims_cst_reg_res and not exit_due_to_high_fail_rate:
565                self.log.info("Step 4: Make voice call.")
566                for cycle in range(
567                    dialed_call_amount, dialed_call_amount+call_cycles):
568                    self.log.info(
569                        '======> CST voice call %s/%s <======',
570                        cycle + 1,
571                        dialed_call_amount+call_cycles)
572                    result = two_phone_call_msim_for_slot(
573                        self.log,
574                        ad_mo,
575                        get_slot_index_from_subid(ad_mo, mo_sub_id),
576                        None,
577                        is_mo_in_call,
578                        ad_mt,
579                        get_slot_index_from_subid(ad_mt, mt_sub_id),
580                        None,
581                        is_mt_in_call)
582                    self.tel_logger.set_result(result.result_value)
583
584                    if not result:
585                        self.log.error(
586                            "Failed to make MO call from %s slot %s to %s slot %s",
587                                ad_mo.serial, mo_slot, ad_mt.serial, mt_slot)
588                        call_result_list.append(False)
589                        call_result_cycle_list.append(cycle + 1)
590                        self._take_bug_report(
591                            self.test_name, begin_time=get_current_epoch_time())
592                    else:
593                        call_result_list.append(True)
594                dialed_call_amount = dialed_call_amount + call_cycles
595
596            if (attempt+1) % CALCULATE_EVERY_N_CYCLES == 0 or (
597                attempt == test_cycles - 1) or exit_due_to_high_fail_rate:
598
599                parsing_fail = parse_cst_reg(
600                    ad_mo, 1-dds_slot, cst_reg_search_intervals)
601                ads[0].log.info('====== Failed cycles of CST registration ======')
602                for each_dict in parsing_fail:
603                    print_nested_dict(ads[0], each_dict)
604
605                ads[0].log.info('====== Failed cycles of CST voice call ======')
606                for index, value in enumerate(call_result_list):
607                    if not value:
608                        ads[0].log.warning(
609                            'CST voice call cycle %s failed.', index+1)
610
611                try:
612                    fail_rate = (
613                        len(call_result_list) - call_result_list.count(True))/len(
614                            call_result_list)
615                    ads[0].log.info('====== Summary ======')
616                    ads[0].log.info(
617                        'Total CST calls: %s',
618                        len(call_result_list))
619                    ads[0].log.warning(
620                        'Total failed CST calls: %s',
621                        call_result_list.count(False))
622                    ads[0].log.info(
623                        'Fail rate of CST voice call: %s', fail_rate)
624                except Exception as e:
625                    ads[0].log.error(
626                        'Fail rate of CST voice call: ERROR (%s)', e)
627
628            self.enable_cst(ads[0], None, False)
629
630            if exit_due_to_high_fail_rate:
631                ads[0].log.error(
632                    'Test case is stopped due to fail rate is greater than 10%.')
633                break
634
635        return True
636
637
638    @test_tracker_info(uuid="5475514a-8897-4dd4-900f-1dd435191d0b")
639    @TelephonyBaseTest.tel_test_wrap
640    def test_msim_psim_mo_cst_call_wifi_preferred(self):
641        return self.msim_cst_call_voice(
642            0,
643            None,
644            mo_rat=["2g", "general"],
645            call_direction="mo",
646            test_cycles=self.user_params.get(
647                "psim_mo_cst_call_wifi_preferred_test_cycle", 1),
648            call_cycles=self.user_params.get("cst_call_cycle", 1))
649
650
651    @test_tracker_info(uuid="40c182b7-af25-428a-bae5-9203eed949d8")
652    @TelephonyBaseTest.tel_test_wrap
653    def test_msim_psim_mo_cst_call_cellular_preferred(self):
654        return self.msim_cst_call_voice(
655            0,
656            None,
657            mo_rat=["2g", "general"],
658            call_direction="mo",
659            wfc_mode=WFC_MODE_CELLULAR_PREFERRED,
660            test_cycles=self.user_params.get(
661                "psim_mo_cst_call_cellular_preferred_test_cycle", 1),
662            call_cycles=self.user_params.get("cst_call_cycle", 1))