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 copy
18import re
19import statistics
20
21from acts import signals
22from acts_contrib.test_utils.tel.tel_defines import INVALID_SUB_ID
23from acts_contrib.test_utils.tel.tel_subscription_utils import get_slot_index_from_data_sub_id
24from acts_contrib.test_utils.tel.tel_subscription_utils import get_slot_index_from_voice_sub_id
25from acts_contrib.test_utils.tel.tel_subscription_utils import get_subid_from_slot_index
26
27SETUP_DATA_CALL = 'SETUP_DATA_CALL'
28SETUP_DATA_CALL_REQUEST = '> SETUP_DATA_CALL'
29SETUP_DATA_CALL_RESPONSE = '< SETUP_DATA_CALL'
30IS_CAPTIVEPORTAL = r'isCaptivePortal: isSuccessful()=true'
31
32DEACTIVATE_DATA_CALL = 'DEACTIVATE_DATA_CALL'
33DEACTIVATE_DATA_CALL_REQUEST = '> DEACTIVATE_DATA_CALL'
34DEACTIVATE_DATA_CALL_RESPONSE = '< DEACTIVATE_DATA_CALL'
35UNSOL_DATA_CALL_LIST_CHANGED = 'UNSOL_DATA_CALL_LIST_CHANGED'
36
37IWLAN_DATA_SERVICE = 'IWlanDataService'
38IWLAN_SETUP_DATA_CALL_REQUEST = '> REQUEST_SETUP_DATA_CALL'
39IWLAN_SETUP_DATA_CALL_RESPONSE = 'setupDataCallResponse'
40IWLAN_SEND_ACK = '> send ACK for serial'
41
42IWLAN_DEACTIVATE_DATA_CALL_REQUEST = '> REQUEST_DEACTIVATE_DATA_CALL'
43IWLAN_DEACTIVATE_DATA_CALL_RESPONSE = 'deactivateDataCallResponse'
44
45SET_PREFERRED_DATA_MODEM = 'SET_PREFERRED_DATA_MODEM'
46
47WHI_IWLAN_DATA_SERVICE = 'IwlanDataService'
48WHI_IWLAN_SETUP_DATA_CALL_REQUEST = r'IwlanDataService\[\d\]: Setup data call'
49WHI_IWLAN_SETUP_DATA_CALL_RESPONSE = r'IwlanDataService\[\d\]: Tunnel opened!'
50WHI_IWLAN_DEACTIVATE_DATA_CALL_REQUEST = r'IwlanDataService\[\d\]: Deactivate data call'
51WHI_IWLAN_DEACTIVATE_DATA_CALL_RESPONSE = r'IwlanDataService\[\d\]: Tunnel closed!'
52
53ON_ENABLE_APN_IMS_SLOT0 = 'DCT-C-0 : onEnableApn: apnType=ims, request type=NORMAL'
54ON_ENABLE_APN_IMS_SLOT1 = 'DCT-C-1 : onEnableApn: apnType=ims, request type=NORMAL'
55ON_ENABLE_APN_IMS_HANDOVER_SLOT0 = 'DCT-C-0 : onEnableApn: apnType=ims, request type=HANDOVER'
56ON_ENABLE_APN_IMS_HANDOVER_SLOT1 = 'DCT-C-1 : onEnableApn: apnType=ims, request type=HANDOVER'
57RADIO_ON_4G_SLOT0 = r'GsmCdmaPhone: \[0\] Event EVENT_RADIO_ON Received'
58RADIO_ON_4G_SLOT1 = r'GsmCdmaPhone: \[1\] Event EVENT_RADIO_ON Received'
59RADIO_ON_IWLAN = 'Switching to new default network.*WIFI CONNECTED'
60WIFI_OFF = 'setWifiEnabled.*enable=false'
61ON_IMS_MM_TEL_CONNECTED_4G_SLOT0 = r'ImsPhone: \[0\].*onImsMmTelConnected imsRadioTech=WWAN'
62ON_IMS_MM_TEL_CONNECTED_4G_SLOT1 = r'ImsPhone: \[1\].*onImsMmTelConnected imsRadioTech=WWAN'
63ON_IMS_MM_TEL_CONNECTED_IWLAN_SLOT0 = r'ImsPhone: \[0\].*onImsMmTelConnected imsRadioTech=WLAN'
64ON_IMS_MM_TEL_CONNECTED_IWLAN_SLOT1 = r'ImsPhone: \[1\].*onImsMmTelConnected imsRadioTech=WLAN'
65
66DEFAULT_MO_SMS_BODY = 'MO SMS body not yet found'
67DEFAULT_MT_SMS_BODY = 'MT SMS body not yet found'
68
69MMS_SERVICE = 'MmsService:'
70MMS_SEND_REQUEST_ID_PATTERN = r'SendRequest@(\S+)'
71MMS_DOWNLOAD_REQUEST_ID_PATTERN = r'DownloadRequest@(\S+)'
72MMS_START_NEW_NW_REQUEST = 'start new network request'
73MMS_200_OK = '200 OK'
74
75SMS_SEND_TEXT_MESSAGE = 'smsSendTextMessage'
76MO_SMS_LOGCAT_PATTERN = r'smsSendTextMessage.*"(\S+)", true|false'
77SEND_SMS = 'SEND_SMS'
78SEND_SMS_REQUEST = '> SEND_SMS'
79SEND_SMS_RESPONSE = '< SEND_SMS'
80SEND_SMS_EXPECT_MORE = 'SEND_SMS_EXPECT_MORE'
81UNSOL_RESPONSE_NEW_SMS = '< UNSOL_RESPONSE_NEW_SMS'
82SMS_RECEIVED = 'SmsReceived'
83MT_SMS_CONTENT_PATTERN = 'sl4a.*?SmsReceived.*?"Text":"(.*?)"'
84
85SEND_SMS_OVER_IMS = r'ImsSmsDispatcher \[(\d)\]'
86SEND_SMS_REQUEST_OVER_IMS = 'sendSms:  mRetryCount'
87SEND_SMS_RESPONSE_OVER_IMS = 'onSendSmsResult token'
88SMS_RECEIVED_OVER_IMS = 'SMS received'
89SMS_RECEIVED_OVER_IMS_SLOT0 = r'ImsSmsDispatcher \[0\]: SMS received'
90SMS_RECEIVED_OVER_IMS_SLOT1 = r'ImsSmsDispatcher \[1\]: SMS received'
91
92IMS_REGISTERED_CST_SLOT0 = 'IMS_REGISTERED.*CrossStackEpdg.*SLID:0'
93IMS_REGISTERED_CST_SLOT1 = 'IMS_REGISTERED.*CrossStackEpdg.*SLID:1'
94ON_IMS_MM_TEL_CONNECTED_IWLAN_SLOT0 = r'ImsPhone: \[0\].*onImsMmTelConnected imsRadioTech=WLAN'
95ON_IMS_MM_TEL_CONNECTED_IWLAN_SLOT1 = r'ImsPhone: \[1\].*onImsMmTelConnected imsRadioTech=WLAN'
96
97
98def print_nested_dict(ad, d):
99    divider = "------"
100    for k, v in d.items():
101        if isinstance(v, dict):
102            ad.log.info('%s %s %s', divider, k, divider)
103            print_nested_dict(ad, v)
104        else:
105            ad.log.info('%s: %s', k, v)
106
107
108def get_slot_from_logcat(msg):
109    """Get slot index from specific pattern in logcat
110
111    Args:
112        msg: logcat message string
113
114    Returns:
115        0 for pSIM or 1 for eSIM
116    """
117    res = re.findall(r'\[(PHONE[\d])\]', msg)
118    try:
119        phone = res[0]
120    except:
121        phone = None
122    return phone
123
124
125def get_apn_from_logcat(msg):
126    """Get APN from logcat
127
128    Args:
129        msg: logcat message string
130
131    Returns:
132        APN
133    """
134    res = re.findall(r'DataProfile=[^/]+/[^/]+/[^/]+/([^/]+)/', msg)
135    try:
136        apn = res[0]
137    except:
138        apn = None
139    return apn
140
141
142def parse_setup_data_call(ad, apn='internet', dds_switch=False):
143    """Search in logcat for lines containing data call setup procedure.
144        Calculate the data call setup time with given APN and validation
145        time on LTE.
146
147    Args:
148        ad: Android object
149        apn: access point name
150        dds_switch: True for switching DDS. Otherwise False.
151
152    Returns:
153        setup_data_call: Dictionary containing data call setup request and
154            response messages for each data call. The format is shown as
155            below:
156            {
157                message_id:
158                {
159                    'request':
160                    {
161                        'message': logcat message body of data call setup
162                            request message
163                        'time_stamp': time stamp in text format
164                        'datetime_obj': datetime object of time stamp
165                        'apn': access point name of this request
166                        'phone': 0 for pSIM or 1 for eSIM
167                    }
168                    'response':
169                    {
170                        'message': logcat message body of data call setup
171                            response message
172                        'time_stamp': time stamp in text format
173                        'datetime_obj': datetime object of time stamp
174                        'cause': failure cause if data call setup failed
175                        'cid': PDP context ID
176                        'ifname': the name of the interface of the network
177                        'phone': 0 for pSIM or 1 for eSIM
178                        'unsol_data_call_list_changed': message of
179                            unsol_data_call_list_changed
180                        'unsol_data_call_list_changed_time': time stamp of
181                            the message unsol_data_call_list_changed
182                        'is_captive_portal': message of LTE validation pass
183                        'data_call_setup_time': time between data call setup
184                            request and unsol_data_call_list_changed
185                        'validation_time_on_lte': time between data call
186                            setup response and LTE validation pass
187                    }
188                }
189            }
190
191        data_call_setup_time_list: List. This is a summary of necessary
192            messages of data call setup procedure The format is shown as
193            below:
194                [
195                    {
196                        'request': logcat message body of data call setup
197                            request message
198                        'response': logcat message body of data call setup
199                            response message
200                        'unsol_data_call_list_changed': message of
201                            unsol_data_call_list_changed
202                        'start': time stamp of data call setup request
203                        'end': time stamp of the message
204                            unsol_data_call_list_changed
205                        'duration': time between data call setup request and
206                            unsol_data_call_list_changed
207                        'validation_time_on_lte': time between data call
208                            setup response and LTE validation pass
209                    }
210                ]
211
212        avg_data_call_setup_time: average of data call setup time
213
214        avg_validation_time_on_lte: average of time for validation time on
215            LTE
216    """
217    ad.log.info('====== Start to search logcat ====== ')
218    logcat = ad.search_logcat(
219        r'%s\|%s\|%s\|%s' % (
220            SET_PREFERRED_DATA_MODEM,
221            SETUP_DATA_CALL,
222            UNSOL_DATA_CALL_LIST_CHANGED, IS_CAPTIVEPORTAL))
223
224    if not logcat:
225        return False
226
227    for msg in logcat:
228        ad.log.info(msg["log_message"])
229
230    dds_slot = get_slot_index_from_data_sub_id(ad)
231
232    set_preferred_data_modem = {}
233    setup_data_call = {}
234    data_call_setup_time_list = []
235    last_message_id = None
236
237    for line in logcat:
238        if line['message_id']:
239            if SET_PREFERRED_DATA_MODEM in line['log_message']:
240                set_preferred_data_modem['message'] = line['log_message']
241                set_preferred_data_modem['time_stamp'] = line['time_stamp']
242                set_preferred_data_modem[
243                    'datetime_obj'] = line['datetime_obj']
244
245            if SETUP_DATA_CALL_REQUEST in line['log_message']:
246                found_apn = get_apn_from_logcat(line['log_message'])
247                if found_apn != apn:
248                    continue
249
250                phone = get_slot_from_logcat(line['log_message'])
251                if not phone:
252                    continue
253
254                if not dds_switch:
255                    if str(dds_slot) not in phone:
256                        continue
257
258                msg_id = line['message_id']
259                last_message_id = line['message_id']
260                if msg_id not in setup_data_call:
261                    setup_data_call[msg_id] = {}
262
263                setup_data_call[msg_id]['request'] = {
264                    'message': line['log_message'],
265                    'time_stamp': line['time_stamp'],
266                    'datetime_obj': line['datetime_obj'],
267                    'apn': found_apn,
268                    'phone': phone}
269
270                if set_preferred_data_modem:
271                    setup_data_call[msg_id]['request'][
272                        'set_preferred_data_modem_message'] = set_preferred_data_modem['message']
273                    setup_data_call[msg_id]['request'][
274                        'set_preferred_data_modem_time_stamp'] = set_preferred_data_modem['time_stamp']
275                    setup_data_call[msg_id]['request'][
276                        'set_preferred_data_modem_datetime_obj'] = set_preferred_data_modem['datetime_obj']
277                    set_preferred_data_modem = {}
278
279            if SETUP_DATA_CALL_RESPONSE in line['log_message']:
280                phone = get_slot_from_logcat(line['log_message'])
281                if not phone:
282                    continue
283
284                if not dds_switch:
285                    if str(dds_slot) not in phone:
286                        continue
287
288                msg_id = line['message_id']
289                if msg_id not in setup_data_call:
290                    continue
291
292                if 'request' not in setup_data_call[msg_id]:
293                    continue
294
295                last_message_id = line['message_id']
296
297                setup_data_call[msg_id]['response'] = {
298                    'message': line['log_message'],
299                    'time_stamp': line['time_stamp'],
300                    'datetime_obj': line['datetime_obj'],
301                    'cause': '0',
302                    'cid': None,
303                    'ifname': None,
304                    'phone': phone,
305                    'unsol_data_call_list_changed': None,
306                    'unsol_data_call_list_changed_time': None,
307                    'is_captive_portal': None,
308                    'data_call_setup_time': None,
309                    'validation_time_on_lte': None}
310
311                res = re.findall(r'cause=(\d+)', line['log_message'])
312                try:
313                    cause = res[0]
314                    setup_data_call[msg_id]['response']['cause'] = cause
315                except:
316                    pass
317
318                res = re.findall(r'cid=(\d+)', line['log_message'])
319                try:
320                    cid = res[0]
321                    setup_data_call[msg_id]['response']['cid'] = cid
322                except:
323                    pass
324
325                res = re.findall(r'ifname=(\S+)', line['log_message'])
326                try:
327                    ifname = res[0]
328                    setup_data_call[msg_id]['response']['ifname'] = ifname
329                except:
330                    pass
331
332        if UNSOL_DATA_CALL_LIST_CHANGED in line['log_message']:
333            if not last_message_id:
334                continue
335
336            phone = get_slot_from_logcat(line['log_message'])
337            if not phone:
338                continue
339
340            if not dds_switch:
341                if str(dds_slot) not in phone:
342                    continue
343
344            if 'request' not in setup_data_call[last_message_id]:
345                continue
346
347            if 'response' not in setup_data_call[last_message_id]:
348                continue
349
350            cid =  setup_data_call[last_message_id]['response']['cid']
351            if 'cid = %s' % cid not in line['log_message']:
352                continue
353
354            if setup_data_call[last_message_id]['response']['cause'] != '0':
355                continue
356
357            if dds_switch:
358                if 'set_preferred_data_modem_message' not in setup_data_call[
359                    last_message_id]['request']:
360                    continue
361                data_call_start_time = setup_data_call[last_message_id][
362                    'request']['set_preferred_data_modem_datetime_obj']
363
364            else:
365                data_call_start_time = setup_data_call[last_message_id][
366                    'request']['datetime_obj']
367
368            data_call_end_time = line['datetime_obj']
369            setup_data_call[last_message_id]['response'][
370                'unsol_data_call_list_changed_time'] = data_call_end_time
371            setup_data_call[last_message_id]['response'][
372                'unsol_data_call_list_changed'] = line['log_message']
373            data_call_setup_time = data_call_end_time - data_call_start_time
374            setup_data_call[last_message_id]['response'][
375                'data_call_setup_time'] = data_call_setup_time.total_seconds()
376
377            if apn == 'ims':
378                data_call_setup_time_list.append(
379                    {'request': setup_data_call[
380                        last_message_id]['request']['message'],
381                    'response': setup_data_call[
382                        last_message_id]['response']['message'],
383                    'unsol_data_call_list_changed': setup_data_call[
384                        last_message_id]['response'][
385                            'unsol_data_call_list_changed'],
386                    'start': data_call_start_time,
387                    'end': data_call_end_time,
388                    'duration': setup_data_call[last_message_id]['response'][
389                        'data_call_setup_time']})
390
391                last_message_id = None
392
393        if IS_CAPTIVEPORTAL in line['log_message']:
394            if not last_message_id:
395                continue
396
397            if 'request' not in setup_data_call[last_message_id]:
398                continue
399
400            if 'response' not in setup_data_call[last_message_id]:
401                continue
402
403            if dds_switch:
404                data_call_start_time = setup_data_call[last_message_id][
405                    'request']['set_preferred_data_modem_datetime_obj']
406
407            else:
408                data_call_start_time = setup_data_call[last_message_id][
409                    'request']['datetime_obj']
410
411            setup_data_call[last_message_id]['response'][
412                'is_captive_portal'] = line['log_message']
413            validation_start_time_on_lte = setup_data_call[
414                last_message_id]['response']['datetime_obj']
415            validation_end_time_on_lte = line['datetime_obj']
416            validation_time_on_lte = (
417                validation_end_time_on_lte - validation_start_time_on_lte).total_seconds()
418            setup_data_call[last_message_id]['response'][
419                'validation_time_on_lte'] = validation_time_on_lte
420
421            data_call_setup_time_list.append(
422                {'request': setup_data_call[last_message_id]['request'][
423                    'message'],
424                'response': setup_data_call[last_message_id]['response'][
425                    'message'],
426                'unsol_data_call_list_changed': setup_data_call[
427                    last_message_id]['response']['unsol_data_call_list_changed'],
428                'start': data_call_start_time,
429                'end': setup_data_call[last_message_id]['response'][
430                    'unsol_data_call_list_changed_time'],
431                'duration': setup_data_call[last_message_id]['response'][
432                    'data_call_setup_time'],
433                'validation_time_on_lte': validation_time_on_lte})
434
435            last_message_id = None
436
437    duration_list = []
438    for item in data_call_setup_time_list:
439        if 'duration' in item:
440            duration_list.append(item['duration'])
441
442    try:
443        avg_data_call_setup_time = statistics.mean(duration_list)
444    except:
445        avg_data_call_setup_time = None
446
447    validation_time_on_lte_list = []
448    for item in data_call_setup_time_list:
449        if 'validation_time_on_lte' in item:
450            validation_time_on_lte_list.append(
451                item['validation_time_on_lte'])
452
453    try:
454        avg_validation_time_on_lte = statistics.mean(
455            validation_time_on_lte_list)
456    except:
457        avg_validation_time_on_lte = None
458
459    return (
460        setup_data_call,
461        data_call_setup_time_list,
462        avg_data_call_setup_time,
463        avg_validation_time_on_lte)
464
465
466def parse_setup_data_call_on_iwlan(ad):
467    """Search in logcat for lines containing data call setup procedure.
468        Calculate the data call setup time with given APN on iwlan.
469
470    Args:
471        ad: Android object
472        apn: access point name
473
474    Returns:
475        setup_data_call: Dictionary containing data call setup request and
476            response messages for each data call. The format is shown as
477            below:
478            {
479                message_id:
480                {
481                    'request':
482                    {
483                        'message': logcat message body of data call setup
484                            request message
485                        'time_stamp': time stamp in text format
486                        'datetime_obj': datetime object of time stamp
487                    }
488                    'response':
489                    {
490                        'message': logcat message body of data call setup
491                            response message
492                        'time_stamp': time stamp in text format
493                        'datetime_obj': datetime object of time stamp
494                        'cause': failure cause if data call setup failed
495                        'data_call_setup_time': time between data call setup
496                            request and response
497                    }
498                }
499            }
500
501        data_call_setup_time_list:
502            List. This is a summary of mecessary messages of data call setup
503                procedure The format is shown as below:
504                [
505                    {
506                        'request': logcat message body of data call setup
507                            request message
508                        'response': logcat message body of data call setup
509                            response message
510                        'start': time stamp of data call setup request
511                        'end': time stamp of data call setup response
512                        'duration': time between data call setup request and
513                            response
514                    }
515                ]
516
517        avg_data_call_setup_time: average of data call setup time
518    """
519    ad.log.info('====== Start to search logcat ====== ')
520    logcat = ad.search_logcat(r'%s\|%s' % (
521        IWLAN_DATA_SERVICE, WHI_IWLAN_DATA_SERVICE))
522
523    found_iwlan_data_service = 1
524    if not logcat:
525        found_iwlan_data_service = 0
526
527    if not found_iwlan_data_service:
528        (
529            setup_data_call,
530            data_call_setup_time_list,
531            avg_data_call_setup_time,
532            _) = parse_setup_data_call(ad, apn='ims')
533
534        return (
535            setup_data_call,
536            data_call_setup_time_list,
537            avg_data_call_setup_time)
538
539    for msg in logcat:
540        ad.log.info(msg["log_message"])
541
542    setup_data_call = {}
543    data_call_setup_time_list = []
544    last_message_id = None
545
546    whi_msg_index = None
547    for line in logcat:
548        serial = None
549        cause = None
550        if IWLAN_SETUP_DATA_CALL_REQUEST in line['log_message']:
551            match_res = re.findall(
552                r'%s:\s(\d+)' % IWLAN_DATA_SERVICE, line['log_message'])
553            if match_res:
554                try:
555                    serial = match_res[0]
556                except:
557                    pass
558
559            if not serial:
560                continue
561
562            msg_id = serial
563            last_message_id = serial
564            if msg_id not in setup_data_call:
565                setup_data_call[msg_id] = {}
566
567            setup_data_call[msg_id]['request'] = {
568                'message': line['log_message'],
569                'time_stamp': line['time_stamp'],
570                'datetime_obj': line['datetime_obj']}
571
572        else:
573            if re.search(
574                WHI_IWLAN_SETUP_DATA_CALL_REQUEST, line['log_message']):
575                if whi_msg_index is None:
576                    whi_msg_index = 0
577                else:
578                    whi_msg_index = whi_msg_index + 1
579
580                if str(whi_msg_index) not in setup_data_call:
581                    setup_data_call[str(whi_msg_index)] = {}
582
583                setup_data_call[str(whi_msg_index)]['request'] = {
584                    'message': line['log_message'],
585                    'time_stamp': line['time_stamp'],
586                    'datetime_obj': line['datetime_obj']}
587
588        if IWLAN_SETUP_DATA_CALL_RESPONSE in line['log_message']:
589            match_res = re.findall(r'Serial = (\d+)', line['log_message'])
590            if match_res:
591                try:
592                    serial = match_res[0]
593                except:
594                    pass
595
596            if serial:
597                msg_id = serial
598            else:
599                msg_id = last_message_id
600
601            if msg_id not in setup_data_call:
602                continue
603
604            if 'request' not in setup_data_call[msg_id]:
605                continue
606
607            setup_data_call[msg_id]['response'] = {
608                'message': None,
609                'time_stamp': None,
610                'datetime_obj': None,
611                'cause': None,
612                'data_call_setup_time': None}
613
614            match_res = re.findall(
615                r'Fail Cause = (\d+)', line['log_message'])
616            if match_res:
617                try:
618                    cause = match_res[0]
619                except:
620                    cause = None
621
622            if cause != '0':
623                continue
624
625            setup_data_call[msg_id]['response']['message'] = line[
626                'log_message']
627            setup_data_call[msg_id]['response']['time_stamp'] = line[
628                'time_stamp']
629            setup_data_call[msg_id]['response']['datetime_obj'] = line[
630                'datetime_obj']
631            setup_data_call[msg_id]['response']['cause'] = 0
632
633            data_call_start_time = setup_data_call[last_message_id][
634                'request']['datetime_obj']
635            data_call_end_time = line['datetime_obj']
636            data_call_setup_time = data_call_end_time - data_call_start_time
637            setup_data_call[last_message_id]['response'][
638                'data_call_setup_time'] = data_call_setup_time.total_seconds()
639
640            data_call_setup_time_list.append(
641                {'request': setup_data_call[last_message_id]['request'][
642                    'message'],
643                'response': setup_data_call[last_message_id]['response'][
644                    'message'],
645                'start': setup_data_call[last_message_id]['request'][
646                    'datetime_obj'],
647                'end': setup_data_call[last_message_id]['response'][
648                    'datetime_obj'],
649                'duration': setup_data_call[last_message_id]['response'][
650                    'data_call_setup_time']})
651
652            last_message_id = None
653
654        else:
655            if re.search(
656                WHI_IWLAN_SETUP_DATA_CALL_RESPONSE, line['log_message']):
657                if whi_msg_index is None:
658                    continue
659
660                if 'response' in setup_data_call[str(whi_msg_index)]:
661                    ad.log.error('Duplicated setup data call response is '
662                    'found or the request message is lost.')
663                    continue
664
665                setup_data_call[str(whi_msg_index)]['response'] = {
666                    'message': line['log_message'],
667                    'time_stamp': line['time_stamp'],
668                    'datetime_obj': line['datetime_obj'],
669                    'data_call_setup_time': None}
670
671                data_call_start_time = setup_data_call[str(whi_msg_index)][
672                    'request']['datetime_obj']
673                data_call_end_time = line['datetime_obj']
674                data_call_setup_time = data_call_end_time - data_call_start_time
675                setup_data_call[str(whi_msg_index)]['response'][
676                    'data_call_setup_time'] = data_call_setup_time.total_seconds()
677
678                data_call_setup_time_list.append(
679                    {'request': setup_data_call[str(whi_msg_index)][
680                        'request']['message'],
681                    'response': setup_data_call[str(whi_msg_index)][
682                        'response']['message'],
683                    'start': setup_data_call[str(whi_msg_index)]['request'][
684                        'datetime_obj'],
685                    'end': setup_data_call[str(whi_msg_index)]['response'][
686                        'datetime_obj'],
687                    'duration': setup_data_call[str(whi_msg_index)][
688                        'response']['data_call_setup_time']})
689
690    duration_list = []
691    for item in data_call_setup_time_list:
692        if 'duration' in item:
693            duration_list.append(item['duration'])
694
695    try:
696        avg_data_call_setup_time = statistics.mean(duration_list)
697    except:
698        avg_data_call_setup_time = None
699
700    ad.log.warning('setup_data_call: %s', setup_data_call)
701    ad.log.warning('duration list: %s', duration_list)
702    ad.log.warning('avg_data_call_setup_time: %s', avg_data_call_setup_time)
703
704    return (
705        setup_data_call,
706        data_call_setup_time_list,
707        avg_data_call_setup_time)
708
709
710def parse_deactivate_data_call(ad):
711    """Search in logcat for lines containing data call deactivation procedure.
712        Calculate the data call deactivation time on LTE.
713
714    Args:
715        ad: Android object
716
717    Returns:
718        deactivate_data_call: Dictionary containing data call deactivation
719            request and response messages for each data call. The format is
720            shown as below:
721            {
722                message_id:
723                {
724                    'request':
725                    {
726                        'message': logcat message body of data call
727                            deactivation request message
728                        'time_stamp': time stamp in text format
729                        'datetime_obj': datetime object of time stamp
730                        'cid': PDP context ID
731                        'phone': 0 for pSIM or 1 for eSIM
732                    }
733                    'response':
734                    {
735                        'message': logcat message body of data call
736                            deactivation response message
737                        'time_stamp': time stamp in text format
738                        'datetime_obj': datetime object of time stamp
739                        'phone': 0 for pSIM or 1 for eSIM
740                        'unsol_data_call_list_changed': message of
741                            unsol_data_call_list_changed
742                        'deactivate_data_call_time': time between data call
743                            deactivation request and unsol_data_call_list_changed
744                    }
745                }
746            }
747
748        deactivate_data_call_time_list: List. This is a summary of necessary
749            messages of data call deactivation procedure The format is shown
750            as below:
751                [
752                    {
753                        'request': logcat message body of data call
754                            deactivation request message
755                        'response': logcat message body of data call
756                            deactivation response message
757                        'unsol_data_call_list_changed': message of
758                            unsol_data_call_list_changed
759                        'start': time stamp of data call deactivation request
760                        'end': time stamp of the message
761                            unsol_data_call_list_changed
762                        'duration': time between data call deactivation
763                            request and unsol_data_call_list_changed
764                    }
765                ]
766
767        avg_deactivate_data_call_time: average of data call deactivation time
768    """
769    ad.log.info('====== Start to search logcat ====== ')
770    logcat = ad.search_logcat(
771        r'%s\|%s' % (DEACTIVATE_DATA_CALL, UNSOL_DATA_CALL_LIST_CHANGED))
772    if not logcat:
773        return False
774
775    for msg in logcat:
776        ad.log.info(msg["log_message"])
777
778    dds_slot = get_slot_index_from_data_sub_id(ad)
779
780    deactivate_data_call = {}
781    deactivate_data_call_time_list = []
782    last_message_id = None
783
784    for line in logcat:
785        if line['message_id']:
786            if DEACTIVATE_DATA_CALL_REQUEST in line['log_message']:
787                phone = get_slot_from_logcat(line['log_message'])
788                if not phone:
789                    continue
790
791                if str(dds_slot) not in phone:
792                    continue
793
794                msg_id = line['message_id']
795                last_message_id = line['message_id']
796                if msg_id not in deactivate_data_call:
797                    deactivate_data_call[msg_id] = {}
798
799                deactivate_data_call[msg_id]['request'] = {
800                    'message': line['log_message'],
801                    'time_stamp': line['time_stamp'],
802                    'datetime_obj': line['datetime_obj'],
803                    'cid': None,
804                    'phone': dds_slot}
805
806                res = re.findall(r'cid = (\d+)', line['log_message'])
807                try:
808                    cid = res[0]
809                    deactivate_data_call[msg_id]['request']['cid'] = cid
810                except:
811                    pass
812
813            if DEACTIVATE_DATA_CALL_RESPONSE in line['log_message']:
814                phone = get_slot_from_logcat(line['log_message'])
815                if not phone:
816                    continue
817
818                if str(dds_slot) not in phone:
819                    continue
820
821                msg_id = line['message_id']
822                if msg_id not in deactivate_data_call:
823                    continue
824
825                if 'request' not in deactivate_data_call[msg_id]:
826                    continue
827
828                last_message_id = line['message_id']
829
830                deactivate_data_call[msg_id]['response'] = {
831                    'message': line['log_message'],
832                    'time_stamp': line['time_stamp'],
833                    'datetime_obj': line['datetime_obj'],
834                    'phone': dds_slot,
835                    'unsol_data_call_list_changed': None,
836                    'deactivate_data_call_time': None}
837
838        if UNSOL_DATA_CALL_LIST_CHANGED in line['log_message']:
839            if not last_message_id:
840                continue
841
842            phone = get_slot_from_logcat(line['log_message'])
843            if not phone:
844                continue
845
846            if str(dds_slot) not in phone:
847                continue
848
849            if 'request' not in deactivate_data_call[last_message_id]:
850                continue
851
852            if 'response' not in deactivate_data_call[last_message_id]:
853                continue
854
855            cid = deactivate_data_call[last_message_id]['request']['cid']
856            if 'cid = %s' % cid not in line['log_message']:
857                continue
858
859            deactivate_data_call_start_time = deactivate_data_call[
860                last_message_id]['request']['datetime_obj']
861            deactivate_data_call_end_time = line['datetime_obj']
862            deactivate_data_call[last_message_id]['response'][
863                'unsol_data_call_list_changed'] = line['log_message']
864            deactivate_data_call_time = (
865                deactivate_data_call_end_time - deactivate_data_call_start_time)
866            deactivate_data_call[last_message_id]['response'][
867                'deactivate_data_call_time'] = deactivate_data_call_time.total_seconds()
868            deactivate_data_call_time_list.append(
869                {'request': deactivate_data_call[last_message_id][
870                    'request']['message'],
871                'response': deactivate_data_call[last_message_id][
872                    'response']['message'],
873                'unsol_data_call_list_changed': deactivate_data_call[
874                    last_message_id]['response'][
875                        'unsol_data_call_list_changed'],
876                'start': deactivate_data_call_start_time,
877                'end': deactivate_data_call_end_time,
878                'duration': deactivate_data_call_time.total_seconds()})
879
880            last_message_id = None
881
882    duration_list = []
883    for item in deactivate_data_call_time_list:
884        if 'duration' in item:
885            duration_list.append(item['duration'])
886
887    try:
888        avg_deactivate_data_call_time = statistics.mean(duration_list)
889    except:
890        avg_deactivate_data_call_time = None
891
892    return (
893        deactivate_data_call,
894        deactivate_data_call_time_list,
895        avg_deactivate_data_call_time)
896
897
898def parse_deactivate_data_call_on_iwlan(ad):
899    """Search in logcat for lines containing data call deactivation procedure.
900        Calculate the data call deactivation time on iwlan.
901
902    Args:
903        ad: Android object
904
905    Returns:
906        deactivate_data_call: Dictionary containing data call deactivation
907            request and response messages for each data call. The format is
908            shown as below:
909            {
910                message_id:
911                {
912                    'request':
913                    {
914                        'message': logcat message body of data call
915                            deactivation request message
916                        'time_stamp': time stamp in text format
917                        'datetime_obj': datetime object of time stamp
918                    }
919                    'response':
920                    {
921                        'message': logcat message body of data call
922                            deactivation response message
923                        'time_stamp': time stamp in text format
924                        'datetime_obj': datetime object of time stamp
925                        'send_ack_for_serial_time': time stamp of ACK
926                        'deactivate_data_call_time': time between data call
927                            deactivation request and ACK
928                    }
929                }
930            }
931
932        deactivate_data_call_time_list: List. This is a summary of necessary
933            messages of data call deactivation procedure The format is shown
934            as below:
935                [
936                    {
937                        'request': logcat message body of data call
938                            deactivation request message
939                        'response': logcat message body of data call
940                            deactivation response message
941                        'start': time stamp of data call deactivation request
942                        'end': time stamp of the ACK
943                        'duration': time between data call deactivation
944                            request and ACK
945                    }
946                ]
947
948        avg_deactivate_data_call_time: average of data call deactivation time
949    """
950    ad.log.info('====== Start to search logcat ====== ')
951    logcat = ad.search_logcat(r'%s\|%s' % (
952        IWLAN_DATA_SERVICE, WHI_IWLAN_DATA_SERVICE))
953
954    found_iwlan_data_service = 1
955    if not logcat:
956        found_iwlan_data_service = 0
957
958    if not found_iwlan_data_service:
959        (
960            deactivate_data_call,
961            deactivate_data_call_time_list,
962            avg_deactivate_data_call_time) = parse_deactivate_data_call(ad)
963
964        return (
965            deactivate_data_call,
966            deactivate_data_call_time_list,
967            avg_deactivate_data_call_time)
968
969    for msg in logcat:
970        ad.log.info(msg["log_message"])
971
972    deactivate_data_call = {}
973    deactivate_data_call_time_list = []
974    last_message_id = None
975
976    whi_msg_index = None
977    for line in logcat:
978        serial = None
979        if IWLAN_DEACTIVATE_DATA_CALL_REQUEST in line['log_message']:
980            match_res = re.findall(
981                r'%s:\s(\d+)' % IWLAN_DATA_SERVICE, line['log_message'])
982            if match_res:
983                try:
984                    serial = match_res[0]
985                except:
986                    serial = None
987
988            if not serial:
989                continue
990
991            msg_id = serial
992            last_message_id = serial
993            if msg_id not in deactivate_data_call:
994                deactivate_data_call[msg_id] = {}
995
996            deactivate_data_call[msg_id]['request'] = {
997                'message': line['log_message'],
998                'time_stamp': line['time_stamp'],
999                'datetime_obj': line['datetime_obj']}
1000        else:
1001            if re.search(WHI_IWLAN_DEACTIVATE_DATA_CALL_REQUEST, line[
1002                'log_message']):
1003                if whi_msg_index is None:
1004                    whi_msg_index = 0
1005                else:
1006                    whi_msg_index = whi_msg_index + 1
1007
1008                if str(whi_msg_index) not in deactivate_data_call:
1009                    deactivate_data_call[str(whi_msg_index)] = {}
1010
1011                deactivate_data_call[str(whi_msg_index)]['request'] = {
1012                    'message': line['log_message'],
1013                    'time_stamp': line['time_stamp'],
1014                    'datetime_obj': line['datetime_obj']}
1015
1016        if IWLAN_DEACTIVATE_DATA_CALL_RESPONSE in line['log_message']:
1017            if 'response' not in deactivate_data_call[last_message_id]:
1018                deactivate_data_call[msg_id]['response'] = {}
1019
1020            deactivate_data_call[msg_id]['response'] = {
1021                'message': line['log_message'],
1022                'time_stamp': line['time_stamp'],
1023                'datetime_obj': line['datetime_obj'],
1024                'send_ack_for_serial_time': None,
1025                'deactivate_data_call_time': None}
1026
1027        else:
1028            if re.search(WHI_IWLAN_DEACTIVATE_DATA_CALL_RESPONSE, line[
1029                'log_message']):
1030                if whi_msg_index is None:
1031                    continue
1032
1033                if 'response' in deactivate_data_call[str(whi_msg_index)]:
1034                    ad.log.error('Duplicated deactivate data call response'
1035                    'is found or the request message is lost.')
1036                    continue
1037
1038                deactivate_data_call[str(whi_msg_index)]['response'] = {
1039                    'message': line['log_message'],
1040                    'time_stamp': line['time_stamp'],
1041                    'datetime_obj': line['datetime_obj'],
1042                    'deactivate_data_call_time': None}
1043
1044                deactivate_data_call_start_time = deactivate_data_call[
1045                    str(whi_msg_index)]['request']['datetime_obj']
1046                deactivate_data_call_end_time = line['datetime_obj']
1047                deactivate_data_call_time = (
1048                    deactivate_data_call_end_time - deactivate_data_call_start_time)
1049                deactivate_data_call[str(whi_msg_index)]['response'][
1050                    'deactivate_data_call_time'] = deactivate_data_call_time.total_seconds()
1051                deactivate_data_call_time_list.append(
1052                    {'request': deactivate_data_call[str(whi_msg_index)][
1053                        'request']['message'],
1054                    'response': deactivate_data_call[str(whi_msg_index)][
1055                        'response']['message'],
1056                    'start': deactivate_data_call_start_time,
1057                    'end': deactivate_data_call_end_time,
1058                    'duration': deactivate_data_call_time.total_seconds()})
1059
1060        if IWLAN_SEND_ACK in line['log_message']:
1061            match_res = re.findall(
1062                r'%s:\s(\d+)' % IWLAN_DATA_SERVICE, line['log_message'])
1063            if match_res:
1064                try:
1065                    serial = match_res[0]
1066                except:
1067                    serial = None
1068
1069            if not serial:
1070                continue
1071
1072            msg_id = serial
1073
1074            if msg_id not in deactivate_data_call:
1075                continue
1076
1077            if 'response' not in deactivate_data_call[msg_id]:
1078                continue
1079
1080            deactivate_data_call[msg_id]['response'][
1081                'send_ack_for_serial_time'] = line['datetime_obj']
1082
1083            deactivate_data_call_start_time = deactivate_data_call[msg_id][
1084                'request']['datetime_obj']
1085            deactivate_data_call_end_time = line['datetime_obj']
1086            deactivate_data_call_time = (
1087                deactivate_data_call_end_time - deactivate_data_call_start_time)
1088            deactivate_data_call[msg_id]['response'][
1089                'deactivate_data_call_time'] = deactivate_data_call_time.total_seconds()
1090            deactivate_data_call_time_list.append(
1091                {'request': deactivate_data_call[msg_id]['request'][
1092                    'message'],
1093                'response': deactivate_data_call[msg_id]['response'][
1094                    'message'],
1095                'start': deactivate_data_call_start_time,
1096                'end': deactivate_data_call_end_time,
1097                'duration': deactivate_data_call_time.total_seconds()})
1098
1099            last_message_id = None
1100
1101    duration_list = []
1102    for item in deactivate_data_call_time_list:
1103        if 'duration' in item:
1104            duration_list.append(item['duration'])
1105
1106    try:
1107        avg_deactivate_data_call_time = statistics.mean(duration_list)
1108    except:
1109        avg_deactivate_data_call_time = None
1110
1111    return (
1112        deactivate_data_call,
1113        deactivate_data_call_time_list,
1114        avg_deactivate_data_call_time)
1115
1116
1117def parse_ims_reg(
1118    ad,
1119    search_intervals=None,
1120    rat='4g',
1121    reboot_or_apm='reboot',
1122    slot=None):
1123    """Search in logcat for lines containing messages about IMS registration.
1124
1125    Args:
1126        ad: Android object
1127        search_intervals: List. Only lines with time stamp in given time
1128            intervals will be parsed.
1129            E.g., [(begin_time1, end_time1), (begin_time2, end_time2)]
1130            Both begin_time and end_time should be datetime object.
1131        rat: "4g" for IMS over LTE or "iwlan" for IMS over Wi-Fi
1132        reboot_or_apm: specify the scenario "reboot" or "apm"
1133        slot: 0 for pSIM and 1 for eSIM
1134
1135    Returns:
1136        (ims_reg, parsing_fail, avg_ims_reg_duration)
1137
1138        ims_reg: List of dictionaries containing found lines for start and
1139            end time stamps. Each dict represents a cycle of the test.
1140
1141            [
1142                {'start': message on start time stamp,
1143                'end': message on end time stamp,
1144                'duration': time difference between start and end}
1145            ]
1146        parsing_fail: List of dictionaries containing the cycle number and
1147            missing messages of each failed cycle
1148
1149            [
1150                'attempt': failed cycle number
1151                'missing_msg' missing messages which should be found
1152            ]
1153        avg_ims_reg_duration: average of the duration in ims_reg
1154
1155    """
1156    if slot is None:
1157        slot = get_slot_index_from_voice_sub_id(ad)
1158        ad.log.info('Default voice slot: %s', slot)
1159    else:
1160        if get_subid_from_slot_index(ad.log, ad, slot) == INVALID_SUB_ID:
1161            ad.log.error('Slot %s is invalid.', slot)
1162            raise signals.TestFailure('Failed',
1163                extras={'fail_reason': 'Slot %s is invalid.' % slot})
1164
1165        ad.log.info('Assigned slot: %s', slot)
1166
1167    start_command = {
1168        'reboot': {
1169            '0': {'4g': ON_ENABLE_APN_IMS_SLOT0,
1170                'iwlan': ON_ENABLE_APN_IMS_HANDOVER_SLOT0 + '\|' + ON_ENABLE_APN_IMS_SLOT0},
1171            '1': {'4g': ON_ENABLE_APN_IMS_SLOT1,
1172                'iwlan': ON_ENABLE_APN_IMS_HANDOVER_SLOT1 + '\|' + ON_ENABLE_APN_IMS_SLOT1}
1173        },
1174        'apm':{
1175            '0': {'4g': RADIO_ON_4G_SLOT0, 'iwlan': RADIO_ON_IWLAN},
1176            '1': {'4g': RADIO_ON_4G_SLOT1, 'iwlan': RADIO_ON_IWLAN}
1177        },
1178        'wifi_off':{
1179            '0': {'4g': WIFI_OFF, 'iwlan': WIFI_OFF},
1180            '1': {'4g': WIFI_OFF, 'iwlan': WIFI_OFF}
1181        },
1182    }
1183
1184    end_command = {
1185        '0': {'4g': ON_IMS_MM_TEL_CONNECTED_4G_SLOT0,
1186            'iwlan': ON_IMS_MM_TEL_CONNECTED_IWLAN_SLOT0},
1187        '1': {'4g': ON_IMS_MM_TEL_CONNECTED_4G_SLOT1,
1188            'iwlan': ON_IMS_MM_TEL_CONNECTED_IWLAN_SLOT1}
1189    }
1190
1191    ad.log.info('====== Start to search logcat ======')
1192    logcat = ad.search_logcat('%s\|%s' % (
1193        start_command[reboot_or_apm][str(slot)][rat],
1194        end_command[str(slot)][rat]))
1195
1196    if not logcat:
1197        raise signals.TestFailure('Failed',
1198            extras={'fail_reason': 'No line matching the given pattern can '
1199            'be found in logcat.'})
1200
1201    for msg in logcat:
1202        ad.log.info(msg["log_message"])
1203
1204    ims_reg = []
1205    ims_reg_duration_list = []
1206    parsing_fail = []
1207
1208    start_command['reboot'] = {
1209        '0': {'4g': ON_ENABLE_APN_IMS_SLOT0,
1210            'iwlan': ON_ENABLE_APN_IMS_HANDOVER_SLOT0 + '|' + ON_ENABLE_APN_IMS_SLOT0},
1211        '1': {'4g': ON_ENABLE_APN_IMS_SLOT1,
1212            'iwlan': ON_ENABLE_APN_IMS_HANDOVER_SLOT1 + '|' + ON_ENABLE_APN_IMS_SLOT1}
1213    }
1214
1215    keyword_dict = {
1216        'start': start_command[reboot_or_apm][str(slot)][rat],
1217        'end': end_command[str(slot)][rat]
1218    }
1219
1220    for attempt, interval in enumerate(search_intervals):
1221        if isinstance(interval, list):
1222            try:
1223                begin_time, end_time = interval
1224            except Exception as e:
1225                ad.log.error(e)
1226                continue
1227
1228            ad.log.info('Parsing begin time: %s', begin_time)
1229            ad.log.info('Parsing end time: %s', end_time)
1230
1231            temp_keyword_dict = copy.deepcopy(keyword_dict)
1232            for line in logcat:
1233                if begin_time and line['datetime_obj'] < begin_time:
1234                    continue
1235
1236                if end_time and line['datetime_obj'] > end_time:
1237                    break
1238
1239                for key in temp_keyword_dict:
1240                    if temp_keyword_dict[key] and not isinstance(
1241                        temp_keyword_dict[key], dict):
1242                        res = re.findall(
1243                            temp_keyword_dict[key], line['log_message'])
1244                        if res:
1245                            ad.log.info('Found: %s', line['log_message'])
1246                            temp_keyword_dict[key] = {
1247                                'message': line['log_message'],
1248                                'time_stamp': line['datetime_obj']}
1249                            break
1250
1251            for key in temp_keyword_dict:
1252                if temp_keyword_dict[key] == keyword_dict[key]:
1253                    ad.log.error(
1254                        '"%s" is missing in cycle %s.',
1255                        keyword_dict[key],
1256                        attempt)
1257                    parsing_fail.append({
1258                        'attempt': attempt,
1259                        'missing_msg': keyword_dict[key]})
1260            try:
1261                ims_reg_duration = (
1262                    temp_keyword_dict['end'][
1263                        'time_stamp'] - temp_keyword_dict[
1264                            'start'][
1265                                'time_stamp']).total_seconds()
1266                ims_reg_duration_list.append(ims_reg_duration)
1267                ims_reg.append({
1268                    'start': temp_keyword_dict['start'][
1269                        'message'],
1270                    'end': temp_keyword_dict['end'][
1271                        'message'],
1272                    'duration': ims_reg_duration})
1273            except Exception as e:
1274                ad.log.error(e)
1275
1276    try:
1277        avg_ims_reg_duration = statistics.mean(ims_reg_duration_list)
1278    except:
1279        avg_ims_reg_duration = None
1280
1281    return ims_reg, parsing_fail, avg_ims_reg_duration
1282
1283
1284def parse_mo_sms(logcat):
1285    """Search in logcat for lines containing messages about SMS sending on
1286        LTE.
1287
1288    Args:
1289        logcat: List containing lines of logcat
1290
1291    Returns:
1292        send_sms: Dictionary containing found lines for each SMS
1293            request and response messages together with their time stamps.
1294            {
1295                'message_id':{
1296                    'request':{
1297                        'message': logcat message body of SMS request
1298                        'time_stamp': time stamp in text format
1299                        'datetime_obj': datetime object of the time stamp
1300                    },
1301                    'response':{
1302                        'message': logcat message body of SMS response
1303                        'time_stamp': time stamp in text format
1304                        'datetime_obj': datetime object of the time stamp
1305                        'sms_delivery_time': time between SMS request and
1306                            response
1307                    }
1308                }
1309            }
1310
1311        summary: the format is listed below:
1312            {
1313                'request': logcat message body of SMS request
1314                'response': logcat message body of SMS response
1315                'unsol_response_new_sms': unsolicited response message upon
1316                    SMS receiving on MT UE
1317                'sms_body': message body of SMS
1318                'mo_start': time stamp of MO SMS request message
1319                'mo_end': time stamp of MO SMS response message
1320                'mo_signal_duration': time between MO SMS request and response
1321                'delivery_time': time between MO SMS request and
1322                    unsol_response_new_sms on MT UE
1323            }
1324
1325        avg_setup_time: average of mo_signal_duration
1326    """
1327    send_sms = {}
1328    summary = []
1329    sms_body = DEFAULT_MO_SMS_BODY
1330    msg_id = None
1331    if not logcat:
1332        return False
1333
1334    for line in logcat:
1335        res = re.findall(MO_SMS_LOGCAT_PATTERN, line['log_message'])
1336        if res:
1337            try:
1338                sms_body = res[0]
1339            except:
1340                sms_body = 'Cannot find MO SMS body'
1341
1342        if line['message_id']:
1343            msg_id = line['message_id']
1344            if SEND_SMS_REQUEST in line[
1345                'log_message'] and SEND_SMS_EXPECT_MORE not in line[
1346                    'log_message']:
1347                if msg_id not in send_sms:
1348                    send_sms[msg_id] = {}
1349
1350                send_sms[msg_id]['sms_body'] = sms_body
1351                sms_body = DEFAULT_MO_SMS_BODY
1352                send_sms[msg_id]['request'] = {
1353                    'message': line['log_message'],
1354                    'time_stamp': line['time_stamp'],
1355                    'datetime_obj': line['datetime_obj']}
1356
1357            if SEND_SMS_RESPONSE in line[
1358                'log_message'] and SEND_SMS_EXPECT_MORE not in line[
1359                    'log_message']:
1360                if msg_id not in send_sms:
1361                    continue
1362
1363                if 'request' not in send_sms[msg_id]:
1364                    continue
1365
1366                if "error" in line['log_message']:
1367                    continue
1368
1369                send_sms[msg_id]['response'] = {
1370                    'message': line['log_message'],
1371                    'time_stamp': line['time_stamp'],
1372                    'datetime_obj': line['datetime_obj'],
1373                    'sms_delivery_time': None}
1374
1375                mo_sms_start_time = send_sms[msg_id]['request'][
1376                    'datetime_obj']
1377                mo_sms_end_time = line['datetime_obj']
1378                sms_delivery_time = mo_sms_end_time - mo_sms_start_time
1379                send_sms[msg_id]['response'][
1380                    'sms_delivery_time'] = sms_delivery_time.total_seconds()
1381                summary.append(
1382                    {'request': send_sms[msg_id]['request']['message'],
1383                    'response': send_sms[msg_id]['response']['message'],
1384                    'unsol_response_new_sms': None,
1385                    'sms_body': send_sms[msg_id]['sms_body'],
1386                    'mo_start': mo_sms_start_time,
1387                    'mo_end': mo_sms_end_time,
1388                    'mo_signal_duration': sms_delivery_time.total_seconds(),
1389                    'delivery_time': None})
1390
1391    duration_list = []
1392    for item in summary:
1393        if 'mo_signal_duration' in item:
1394            duration_list.append(item['mo_signal_duration'])
1395
1396    try:
1397        avg_setup_time = statistics.mean(duration_list)
1398    except:
1399        avg_setup_time = None
1400
1401    return send_sms, summary, avg_setup_time
1402
1403
1404def parse_mo_sms_iwlan(logcat):
1405    """Search in logcat for lines containing messages about SMS sending on
1406        iwlan.
1407
1408    Args:
1409        logcat: List containing lines of logcat
1410
1411    Returns:
1412        send_sms: Dictionary containing found lines for each SMS
1413            request and response messages together with their time stamps.
1414            {
1415                'message_id':{
1416                    'request':{
1417                        'message': logcat message body of SMS request
1418                        'time_stamp': time stamp in text format
1419                        'datetime_obj': datetime object of the time stamp
1420                    },
1421                    'response':{
1422                        'message': logcat message body of SMS response
1423                        'time_stamp': time stamp in text format
1424                        'datetime_obj': datetime object of the time stamp
1425                        'sms_delivery_time': time between SMS request and
1426                            response
1427                    }
1428                }
1429            }
1430
1431        summary: List containing dictionaries for each SMS. The format is
1432            listed below:
1433            [
1434                {
1435                    'request': logcat message body of SMS request
1436                    'response': logcat message body of SMS response
1437                    'sms_body': message body of SMS
1438                    'mo_start': time stamp of MO SMS request message
1439                    'mo_end': time stamp of MO SMS response message
1440                    'mo_signal_duration': time between MO SMS request and
1441                        response
1442                    'delivery_time': time between MO SMS request and
1443                        MT SMS received message
1444                }
1445            ]
1446
1447        avg_setup_time: average of mo_signal_duration
1448    """
1449    send_sms = {}
1450    summary = []
1451    sms_body = DEFAULT_MO_SMS_BODY
1452    msg_id = None
1453
1454    if not logcat:
1455        return False
1456
1457    for line in logcat:
1458        res = re.findall(MO_SMS_LOGCAT_PATTERN, line['log_message'])
1459        if res:
1460            try:
1461                sms_body = res[0]
1462            except:
1463                sms_body = 'Cannot find MO SMS body'
1464
1465        if SEND_SMS_REQUEST_OVER_IMS in line['log_message']:
1466            if msg_id is None:
1467                msg_id = '0'
1468            else:
1469                msg_id = str(int(msg_id) + 1)
1470
1471            if msg_id not in send_sms:
1472                send_sms[msg_id] = {}
1473
1474            send_sms[msg_id]['sms_body'] = sms_body
1475            sms_body = DEFAULT_MO_SMS_BODY
1476            send_sms[msg_id]['request'] = {
1477                'message': line['log_message'],
1478                'time_stamp': line['time_stamp'],
1479                'datetime_obj': line['datetime_obj']}
1480
1481        if SEND_SMS_RESPONSE_OVER_IMS in line['log_message']:
1482
1483            if msg_id not in send_sms:
1484                continue
1485
1486            if 'request' not in send_sms[msg_id]:
1487                continue
1488
1489            if "error" in line['log_message']:
1490                continue
1491
1492            send_sms[msg_id]['response'] = {
1493                'message': line['log_message'],
1494                'time_stamp': line['time_stamp'],
1495                'datetime_obj': line['datetime_obj'],
1496                'sms_delivery_time': None}
1497
1498            mo_sms_start_time = send_sms[msg_id]['request'][
1499                'datetime_obj']
1500            mo_sms_end_time = line['datetime_obj']
1501            sms_delivery_time = mo_sms_end_time - mo_sms_start_time
1502            send_sms[msg_id]['response'][
1503                'sms_delivery_time'] = sms_delivery_time.total_seconds()
1504            summary.append(
1505                {'request': send_sms[msg_id]['request']['message'],
1506                'response': send_sms[msg_id]['response']['message'],
1507                'unsol_response_new_sms': None,
1508                'sms_body': send_sms[msg_id]['sms_body'],
1509                'mo_start': mo_sms_start_time,
1510                'mo_end': mo_sms_end_time,
1511                'mo_signal_duration': sms_delivery_time.total_seconds(),
1512                'delivery_time': None})
1513
1514    duration_list = []
1515    for item in summary:
1516        if 'mo_signal_duration' in item:
1517            duration_list.append(item['mo_signal_duration'])
1518
1519    try:
1520        avg_setup_time = statistics.mean(duration_list)
1521    except:
1522        avg_setup_time = None
1523
1524    return send_sms, summary, avg_setup_time
1525
1526
1527def parse_mt_sms(logcat):
1528    """Search in logcat for lines containing messages about SMS receiving on
1529        LTE.
1530
1531    Args:
1532        logcat: List containing lines of logcat
1533
1534    Returns:
1535        received_sms_list: List containing dictionaries for each received
1536            SMS. The format is listed below:
1537        [
1538            {
1539                'message': logcat message body of unsolicited response
1540                    message
1541                'sms_body': message body of SMS
1542                'time_stamp': time stamp of unsolicited response message in
1543                        text format
1544                'datetime_obj': datetime object of the time stamp
1545                'sms_delivery_time': time between SMS request and
1546                    response
1547            }
1548        ]
1549    """
1550    received_sms_list = []
1551    if not logcat:
1552        return False
1553
1554    for line in logcat:
1555        if UNSOL_RESPONSE_NEW_SMS in line['log_message']:
1556
1557            # if received_sms_list:
1558            #     if received_sms_list[-1]['sms_body'] is None:
1559            #         del received_sms_list[-1]
1560
1561            received_sms_list.append(
1562                {'message': line['log_message'],
1563                'sms_body': DEFAULT_MT_SMS_BODY,
1564                'time_stamp': line['time_stamp'],
1565                'datetime_obj': line['datetime_obj']})
1566        else:
1567            res = re.findall(MT_SMS_CONTENT_PATTERN, line['log_message'])
1568
1569            if res:
1570                try:
1571                    sms_body = res[0]
1572                except:
1573                    sms_body = 'Cannot find MT SMS body'
1574
1575                if received_sms_list[-1]['sms_body'] == DEFAULT_MT_SMS_BODY:
1576                    received_sms_list[-1]['sms_body'] = sms_body
1577                    continue
1578
1579    return received_sms_list
1580
1581
1582def parse_mt_sms_iwlan(logcat):
1583    """Search in logcat for lines containing messages about SMS receiving on
1584        iwlan.
1585
1586    Args:
1587        logcat: List containing lines of logcat
1588
1589    Returns:
1590        received_sms_list: List containing dictionaries for each received
1591            SMS. The format is listed below:
1592        [
1593            {
1594                'message': logcat message body of SMS received message
1595                'sms_body': message body of SMS
1596                'time_stamp': time stamp of SMS received message in
1597                        text format
1598                'datetime_obj': datetime object of the time stamp
1599            }
1600        ]
1601    """
1602    received_sms_list = []
1603    if not logcat:
1604        return False
1605
1606    for line in logcat:
1607        if re.findall(
1608            SMS_RECEIVED_OVER_IMS_SLOT0 + '|' + SMS_RECEIVED_OVER_IMS_SLOT1,
1609            line['log_message']):
1610            received_sms_list.append(
1611                {'message': line['log_message'],
1612                'sms_body': DEFAULT_MT_SMS_BODY,
1613                'time_stamp': line['time_stamp'],
1614                'datetime_obj': line['datetime_obj']})
1615        else:
1616            res = re.findall(MT_SMS_CONTENT_PATTERN, line['log_message'])
1617
1618            if res:
1619                try:
1620                    sms_body = res[0]
1621                except:
1622                    sms_body = 'Cannot find MT SMS body'
1623
1624                if received_sms_list[-1]['sms_body'] == DEFAULT_MT_SMS_BODY:
1625                    received_sms_list[-1]['sms_body'] = sms_body
1626                    continue
1627
1628    return received_sms_list
1629
1630
1631def parse_sms_delivery_time(log, ad_mo, ad_mt, rat='4g'):
1632    """Calculate the SMS delivery time (time between MO SMS request and MT
1633        unsolicited response message or MT SMS received message) from logcat
1634        of both MO and MT UE.
1635
1636    Args:
1637        ad_mo: MO Android object
1638        ad_mt: MT Android object
1639        rat: '4g' for LTE and 'iwlan' for iwlan
1640
1641    Returns:
1642        None
1643    """
1644    ad_mo.log.info('====== Start to search logcat ====== ')
1645    mo_logcat = ad_mo.search_logcat(
1646        r'%s\|%s\|%s\|%s' % (
1647            SMS_SEND_TEXT_MESSAGE,
1648            SEND_SMS,
1649            SEND_SMS_REQUEST_OVER_IMS,
1650            SEND_SMS_RESPONSE_OVER_IMS))
1651    ad_mt.log.info('====== Start to search logcat ====== ')
1652    mt_logcat = ad_mt.search_logcat(
1653        r'%s\|%s\|%s' % (
1654            UNSOL_RESPONSE_NEW_SMS, SMS_RECEIVED, SMS_RECEIVED_OVER_IMS))
1655
1656    for msg in mo_logcat:
1657        ad_mo.log.info(msg["log_message"])
1658    for msg in mt_logcat:
1659        ad_mt.log.info(msg["log_message"])
1660
1661    if rat == 'iwlan':
1662        _, mo_sms_summary, avg = parse_mo_sms_iwlan(mo_logcat)
1663        received_sms_list = parse_mt_sms_iwlan(mt_logcat)
1664    else:
1665        _, mo_sms_summary, avg = parse_mo_sms(mo_logcat)
1666        received_sms_list = parse_mt_sms(mt_logcat)
1667
1668    sms_delivery_time = []
1669    for mo_sms in mo_sms_summary:
1670        for mt_sms in received_sms_list:
1671            if mo_sms['sms_body'] == mt_sms['sms_body']:
1672                mo_sms['delivery_time'] = (
1673                    mt_sms['datetime_obj'] - mo_sms['mo_start']).total_seconds()
1674                mo_sms['unsol_response_new_sms'] = mt_sms['message']
1675                sms_delivery_time.append(mo_sms['delivery_time'])
1676
1677    try:
1678        avg_sms_delivery_time = statistics.mean(sms_delivery_time)
1679    except:
1680        avg_sms_delivery_time = None
1681
1682    ad_mo.log.info('====== MO SMS summary ======')
1683    for item in mo_sms_summary:
1684        ad_mo.log.info('------------------')
1685        print_nested_dict(ad_mo, item)
1686    ad_mt.log.info('====== Received SMS list ======')
1687    for item in received_sms_list:
1688        ad_mt.log.info('------------------')
1689        print_nested_dict(ad_mt, item)
1690
1691    ad_mo.log.info('%s SMS were actually sent.', len(mo_sms_summary))
1692    ad_mt.log.info('%s SMS were actually received.', len(received_sms_list))
1693    ad_mo.log.info('Average MO SMS setup time: %.2f sec.', avg)
1694    log.info(
1695        'Average SMS delivery time: %.2f sec.', avg_sms_delivery_time)
1696
1697
1698def parse_mms(ad_mo, ad_mt):
1699    """Search in logcat for lines containing messages about SMS sending and
1700        receiving. Calculate MO & MT MMS setup time.
1701
1702    Args:
1703        ad_mo: MO Android object
1704        ad_mt: MT Android object
1705
1706    Returns:
1707        send_mms: Dictionary containing each sent MMS. The format is shown
1708            as below:
1709            {
1710                mms_msg_id:
1711                {
1712                    MMS_START_NEW_NW_REQUEST:
1713                    {
1714                        'time_stamp': time stamp of MMS request on MO UE in
1715                        text format
1716                        'datetime_obj': datetime object of time stamp
1717                    },
1718                    MMS_200_OK:
1719                    {
1720                        'time_stamp': time stamp of '200 OK' for MMS request
1721                        in text format
1722                        'datetime_obj': datetime object of time stamp
1723                        'setup_time': MO MMS setup time. Time between MMS
1724                        request and 200 OK
1725                    }
1726                }
1727
1728            }
1729
1730        mo_avg_setup_time: average of MO MMS setup time
1731
1732        receive_mms: Dictionary containing each received MMS. The format is
1733            shown as below:
1734            {
1735                mms_msg_id:
1736                {
1737                    MMS_START_NEW_NW_REQUEST:
1738                    {
1739                        'time_stamp': time stamp of MMS request on MT UE in
1740                        text format
1741                        'datetime_obj': datetime object of time stamp
1742                    },
1743                    MMS_200_OK:
1744                    {
1745                        'time_stamp': time stamp of '200 OK' for MMS request
1746                        in text format
1747                        'datetime_obj': datetime object of time stamp
1748                        'setup_time': MT MMS setup time. Time between MMS
1749                        request and 200 OK
1750                    }
1751                }
1752
1753            }
1754
1755        mt_avg_setup_time: average of MT MMS setup time
1756    """
1757    send_mms = {}
1758    receive_mms = {}
1759    mo_setup_time_list = []
1760    mt_setup_time_list = []
1761
1762    ad_mo.log.info('====== Start to search logcat ====== ')
1763    mo_logcat = ad_mo.search_logcat(MMS_SERVICE)
1764    for msg in mo_logcat:
1765        ad_mo.log.info(msg["log_message"])
1766
1767    ad_mt.log.info('====== Start to search logcat ====== ')
1768    mt_logcat = ad_mt.search_logcat(MMS_SERVICE)
1769    for msg in mt_logcat:
1770        ad_mt.log.info(msg["log_message"])
1771
1772    if not mo_logcat or not mt_logcat:
1773        return False
1774
1775    for line in mo_logcat:
1776        find_res = re.findall(
1777            MMS_SEND_REQUEST_ID_PATTERN, line['log_message'])
1778
1779        message_id = None
1780        try:
1781            message_id = find_res[0]
1782        except:
1783            pass
1784
1785        if message_id:
1786            mms_msg_id = message_id
1787            if mms_msg_id not in send_mms:
1788                send_mms[mms_msg_id] = {}
1789            if MMS_START_NEW_NW_REQUEST in line['log_message']:
1790                send_mms[mms_msg_id][MMS_START_NEW_NW_REQUEST] = {
1791                    'time_stamp': line['time_stamp'],
1792                    'datetime_obj': line['datetime_obj']}
1793
1794            if MMS_200_OK in line['log_message']:
1795                send_mms[mms_msg_id][MMS_200_OK] = {
1796                    'time_stamp': line['time_stamp'],
1797                    'datetime_obj': line['datetime_obj'],
1798                    'setup_time': None}
1799
1800                if MMS_START_NEW_NW_REQUEST in send_mms[mms_msg_id]:
1801                    setup_time = line['datetime_obj'] - send_mms[mms_msg_id][
1802                        MMS_START_NEW_NW_REQUEST]['datetime_obj']
1803                    send_mms[mms_msg_id][MMS_200_OK][
1804                        'setup_time'] = setup_time.total_seconds()
1805                    mo_setup_time_list.append(setup_time.total_seconds())
1806
1807    for line in mt_logcat:
1808        find_res = re.findall(
1809            MMS_DOWNLOAD_REQUEST_ID_PATTERN, line['log_message'])
1810
1811        message_id = None
1812        try:
1813            message_id = find_res[0]
1814        except:
1815            pass
1816
1817        if message_id:
1818            mms_msg_id = message_id
1819            if mms_msg_id not in receive_mms:
1820                receive_mms[mms_msg_id] = {}
1821            if MMS_START_NEW_NW_REQUEST in line['log_message']:
1822                receive_mms[mms_msg_id][MMS_START_NEW_NW_REQUEST] = {
1823                    'time_stamp': line['time_stamp'],
1824                    'datetime_obj': line['datetime_obj']}
1825
1826            if MMS_200_OK in line['log_message']:
1827                receive_mms[mms_msg_id][MMS_200_OK] = {
1828                    'time_stamp': line['time_stamp'],
1829                    'datetime_obj': line['datetime_obj'],
1830                    'setup_time': None}
1831
1832                if MMS_START_NEW_NW_REQUEST in receive_mms[mms_msg_id]:
1833                    setup_time = line['datetime_obj'] - receive_mms[
1834                        mms_msg_id][MMS_START_NEW_NW_REQUEST]['datetime_obj']
1835                    receive_mms[mms_msg_id][MMS_200_OK][
1836                        'setup_time'] = setup_time.total_seconds()
1837                    mt_setup_time_list.append(setup_time.total_seconds())
1838
1839    try:
1840        mo_avg_setup_time = statistics.mean(mo_setup_time_list)
1841    except:
1842        mo_avg_setup_time = None
1843
1844    try:
1845        mt_avg_setup_time = statistics.mean(mt_setup_time_list)
1846    except:
1847        mt_avg_setup_time = None
1848
1849    return send_mms, mo_avg_setup_time, receive_mms, mt_avg_setup_time
1850
1851
1852def parse_cst_reg(ad, slot, search_intervals=None):
1853    """ Check if IMS CST and WFC is registered at given slot by parsing logcat.
1854
1855        Args:
1856            ad: Android object
1857            slot: 0 for pSIM and 1 for eSIM
1858            search_intervals: List. Only lines with time stamp in given time
1859                intervals will be parsed.
1860                E.g., [(begin_time1, end_time1), (begin_time2, end_time2)]
1861                Both begin_time and end_time should be datetime object.
1862
1863        Returns: List of attampt number and error messages of not found pattern
1864            of failing cycles
1865    """
1866    cst = {
1867        'ims_registered': {
1868            '0': IMS_REGISTERED_CST_SLOT0,
1869            '1': IMS_REGISTERED_CST_SLOT1
1870        },
1871        'iwlan': {
1872            '0': ON_IMS_MM_TEL_CONNECTED_IWLAN_SLOT0,
1873            '1': ON_IMS_MM_TEL_CONNECTED_IWLAN_SLOT1
1874        }
1875    }
1876    ad.log.info('====== Start to search logcat ====== ')
1877    logcat = ad.search_logcat(
1878        '%s\|%s' % (
1879            cst['ims_registered'][str(slot)], cst['iwlan'][str(slot)]))
1880
1881    for line in logcat:
1882        msg = line["log_message"]
1883        ad.log.info(msg)
1884
1885    parsing_fail = []
1886    keyword_dict = {
1887        'ims_registered': cst['ims_registered'][str(slot)],
1888        'iwlan': cst['iwlan'][str(slot)]
1889    }
1890    for attempt, interval in enumerate(search_intervals):
1891        if isinstance(interval, list):
1892            try:
1893                begin_time, end_time = interval
1894            except Exception as e:
1895                ad.log.error(e)
1896                continue
1897
1898            ad.log.info('Parsing begin time: %s', begin_time)
1899            ad.log.info('Parsing end time: %s', end_time)
1900
1901            temp_keyword_dict = copy.deepcopy(keyword_dict)
1902            for line in logcat:
1903                if begin_time and line['datetime_obj'] < begin_time:
1904                    continue
1905
1906                if end_time and line['datetime_obj'] > end_time:
1907                    break
1908
1909                for key in temp_keyword_dict:
1910                    if temp_keyword_dict[key] and not isinstance(
1911                        temp_keyword_dict[key], dict):
1912                        res = re.findall(
1913                            temp_keyword_dict[key], line['log_message'])
1914                        if res:
1915                            ad.log.info('Found: %s', line['log_message'])
1916                            temp_keyword_dict[key] = {
1917                                'message': line['log_message'],
1918                                'time_stamp': line['datetime_obj']}
1919                            break
1920
1921            for key in temp_keyword_dict:
1922                if temp_keyword_dict[key] == keyword_dict[key]:
1923                    ad.log.error(
1924                        '"%s" is missing in cycle %s.',
1925                        keyword_dict[key],
1926                        attempt+1)
1927                    parsing_fail.append({
1928                        'attempt': attempt+1,
1929                        'missing_msg': keyword_dict[key]})
1930
1931    return parsing_fail
1932
1933
1934def check_ims_cst_reg(ad, slot, search_interval=None):
1935    """ Check if IMS CST is registered at given slot by parsing logcat.
1936
1937        Args:
1938            ad: Android object
1939            slot: 0 for pSIM and 1 for eSIM
1940            search_intervals: List. Only lines with time stamp in given time
1941                intervals will be parsed.
1942                E.g., [(begin_time1, end_time1), (begin_time2, end_time2)]
1943                Both begin_time and end_time should be datetime object.
1944
1945        Returns: True for successful registration. Otherwise False
1946    """
1947    ims_cst_reg = {
1948        '0': IMS_REGISTERED_CST_SLOT0,
1949        '1': IMS_REGISTERED_CST_SLOT1
1950    }
1951    logcat = ad.search_logcat('%s' % ims_cst_reg[str(slot)])
1952    if isinstance(search_interval, list):
1953        try:
1954            begin_time, end_time = search_interval
1955        except Exception as e:
1956            ad.log.error(e)
1957
1958        for line in logcat:
1959            if begin_time and line['datetime_obj'] < begin_time:
1960                continue
1961
1962            if end_time and line['datetime_obj'] > end_time:
1963                break
1964
1965            res = re.findall(ims_cst_reg[str(slot)], line['log_message'])
1966            if res:
1967                ad.log.info(
1968                    'IMS CST is registered due to following message '
1969                    'found: %s', line['log_message'])
1970                return True
1971    return False