1#!/usr/bin/env python3.4
2#
3#   Copyright 2022 - The Android Open Source Project
4#
5#   Licensed under the Apache License, Version 2.0 (the 'License');
6#   you may not use this file except in compliance with the License.
7#   You may obtain a copy of the License at
8#
9#       http://www.apache.org/licenses/LICENSE-2.0
10#
11#   Unless required by applicable law or agreed to in writing, software
12#   distributed under the License is distributed on an 'AS IS' BASIS,
13#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14#   See the License for the specific language governing permissions and
15#   limitations under the License.
16
17import collections
18import logging
19import os
20import re
21import time
22from queue import Empty
23from acts.controllers.adb_lib.error import AdbError
24from acts.controllers.android_lib.tel import tel_utils
25
26PCC_PRESET_MAPPING = {
27    'N257': {
28        'low': 2054999,
29        'mid': 2079165,
30        'high': 2090832
31    },
32    'N258': {
33        'low': 2017499,
34        'mid': 2043749,
35        'high': 2057499
36    },
37    'N260': {
38        'low': 2229999,
39        'mid': 2254165,
40        'high': 2265832
41    },
42    'N261': {
43        'low': 2071667
44    }
45}
46
47DUPLEX_MODE_TO_BAND_MAPPING = {
48    'LTE': {
49        'FDD': [
50            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 19, 20, 21,
51            22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 65, 66, 67, 68, 69, 70,
52            71, 72, 73, 74, 75, 76, 85, 252, 255
53        ],
54        'TDD': [
55            33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 45, 46, 47, 48,
56            50, 51, 53
57        ]
58    },
59    'NR5G': {
60        'FDD': [
61            'N1', 'N2', 'N3', 'N5', 'N7', 'N8', 'N12', 'N13', 'N14', 'N18',
62            'N20', 'N25', 'N26', 'N28', 'N30', 'N65', 'N66', 'N70', 'N71',
63            'N74'
64        ],
65        'TDD': [
66            'N34', 'N38', 'N39', 'N40', 'N41', 'N48', 'N50', 'N51', 'N53',
67            'N77', 'N78', 'N79', 'N90', 'N257', 'N258', 'N259', 'N260', 'N261'
68        ]
69    },
70}
71
72SHORT_SLEEP = 1
73LONG_SLEEP = 10
74
75POWER_STATS_DUMPSYS_CMD = 'dumpsys android.hardware.power.stats.IPowerStats/default delta'
76
77
78class ObjNew(object):
79    """Create a random obj with unknown attributes and value.
80
81    """
82
83    def __init__(self, **kwargs):
84        self.__dict__.update(kwargs)
85
86    def __contains__(self, item):
87        """Function to check if one attribute is contained in the object.
88
89        Args:
90            item: the item to check
91        Return:
92            True/False
93        """
94        return hasattr(self, item)
95
96
97def extract_test_id(testcase_params, id_fields):
98    test_id = collections.OrderedDict(
99        (param, testcase_params[param]) for param in id_fields)
100    return test_id
101
102
103def generate_endc_combo_config_from_string(endc_combo_str):
104    """Function to generate ENDC combo config from combo string
105
106    Args:
107        endc_combo_str: ENDC combo descriptor (e.g. B48A[4];A[1]+N5A[2];A[1])
108    Returns:
109        endc_combo_config: dictionary with all ENDC combo settings
110    """
111    endc_combo_config = collections.OrderedDict()
112    endc_combo_config['endc_combo_name'] = endc_combo_str
113    endc_combo_str = endc_combo_str.replace(' ', '')
114    endc_combo_list = endc_combo_str.split('+')
115    cell_config_list = list()
116    lte_cell_count = 0
117    nr_cell_count = 0
118    lte_scc_list = []
119    nr_dl_carriers = []
120    nr_ul_carriers = []
121    lte_dl_carriers = []
122    lte_ul_carriers = []
123
124    cell_config_regex = re.compile(
125        r'(?P<cell_type>[B,N])(?P<band>[0-9]+)(?P<bandwidth_class>[A-Z])\[bw=(?P<dl_bandwidth>[0-9]+)\]'
126        r'(\[ch=)?(?P<channel>[0-9]+)?\]?'
127        r'\[ant=(?P<dl_mimo_config>[0-9]+),?(?P<transmission_mode>[TM0-9]+)?,?(?P<num_layers>[TM0-9]+)?,?(?P<num_codewords>[TM0-9]+)?\];?'
128        r'(?P<ul_bandwidth_class>[A-Z])?(\[ant=)?(?P<ul_mimo_config>[0-9])?(\])?'
129    )
130    for cell_string in endc_combo_list:
131        cell_config = re.match(cell_config_regex, cell_string).groupdict()
132        if cell_config['cell_type'] == 'B':
133            # Configure LTE specific parameters
134            cell_config['cell_type'] = 'LTE'
135            lte_cell_count = lte_cell_count + 1
136            cell_config['cell_number'] = lte_cell_count
137            if cell_config['cell_number'] == 1:
138                cell_config['pcc'] = 1
139                endc_combo_config['lte_pcc'] = cell_config['cell_number']
140            else:
141                cell_config['pcc'] = 0
142                lte_scc_list.append(cell_config['cell_number'])
143            cell_config['duplex_mode'] = 'FDD' if int(
144                cell_config['band']
145            ) in DUPLEX_MODE_TO_BAND_MAPPING['LTE']['FDD'] else 'TDD'
146            cell_config['dl_mimo_config'] = 'D{nss}U{nss}'.format(
147                nss=cell_config['dl_mimo_config'])
148            cell_config['dl_subframe_allocation'] = [1] * 10
149            lte_dl_carriers.append(cell_config['cell_number'])
150        else:
151            # Configure NR specific parameters
152            cell_config['cell_type'] = 'NR5G'
153            nr_cell_count = nr_cell_count + 1
154            cell_config['cell_number'] = nr_cell_count
155            nr_dl_carriers.append(cell_config['cell_number'])
156            #TODO: fix NSA/SA indicator
157            cell_config['nr_cell_type'] = 'NSA'
158            cell_config['band'] = 'N' + cell_config['band']
159            cell_config['duplex_mode'] = 'FDD' if cell_config[
160                'band'] in DUPLEX_MODE_TO_BAND_MAPPING['NR5G']['FDD'] else 'TDD'
161            cell_config['subcarrier_spacing'] = 'MU0' if cell_config[
162                'duplex_mode'] == 'FDD' else 'MU1'
163            cell_config['dl_mimo_config'] = 'N{nss}X{nss}'.format(
164                nss=cell_config['dl_mimo_config'])
165
166        cell_config['dl_bandwidth_class'] = cell_config['bandwidth_class']
167        cell_config['dl_bandwidth'] = 'BW' + cell_config['dl_bandwidth']
168        cell_config[
169            'ul_enabled'] = 1 if cell_config['ul_bandwidth_class'] else 0
170        if cell_config['ul_enabled']:
171            cell_config['ul_mimo_config'] = 'N{nss}X{nss}'.format(
172                nss=cell_config['ul_mimo_config'])
173            if cell_config['cell_type'] == 'LTE':
174                lte_ul_carriers.append(cell_config['cell_number'])
175            elif cell_config['cell_type'] == 'NR5G':
176                nr_ul_carriers.append(cell_config['cell_number'])
177        cell_config_list.append(cell_config)
178    endc_combo_config['lte_cell_count'] = lte_cell_count
179    endc_combo_config['nr_cell_count'] = nr_cell_count
180    endc_combo_config['nr_dl_carriers'] = nr_dl_carriers
181    endc_combo_config['nr_ul_carriers'] = nr_ul_carriers
182    endc_combo_config['cell_list'] = cell_config_list
183    endc_combo_config['lte_scc_list'] = lte_scc_list
184    endc_combo_config['lte_dl_carriers'] = lte_dl_carriers
185    endc_combo_config['lte_ul_carriers'] = lte_ul_carriers
186    return endc_combo_config
187
188
189def generate_endc_combo_config_from_csv_row(test_config):
190    """Function to generate ENDC combo config from CSV test config
191
192    Args:
193        test_config: dict containing ENDC combo config from CSV
194    Returns:
195        endc_combo_config: dictionary with all ENDC combo settings
196    """
197    endc_combo_config = collections.OrderedDict()
198    lte_cell_count = 0
199    nr_cell_count = 0
200    lte_scc_list = []
201    nr_dl_carriers = []
202    nr_ul_carriers = []
203    lte_dl_carriers = []
204    lte_ul_carriers = []
205
206    cell_config_list = []
207    if 'lte_band' in test_config and test_config['lte_band']:
208        lte_cell = {
209            'cell_type':
210            'LTE',
211            'cell_number':
212            1,
213            'pcc':
214            1,
215            'band':
216            test_config['lte_band'],
217            'dl_bandwidth':
218            test_config['lte_bandwidth'],
219            'ul_enabled':
220            1,
221            'duplex_mode':
222            test_config['lte_duplex_mode'],
223            'dl_mimo_config':
224            'D{nss}U{nss}'.format(nss=test_config['lte_dl_mimo_config']),
225            'ul_mimo_config':
226            'D{nss}U{nss}'.format(nss=test_config['lte_ul_mimo_config']),
227            'transmission_mode':
228            test_config['lte_tm_mode'],
229            'num_codewords':
230            test_config['lte_codewords'],
231            'num_layers':
232            test_config['lte_layers'],
233            'dl_subframe_allocation':
234            test_config.get('dl_subframe_allocation', [1] * 10)
235        }
236        cell_config_list.append(lte_cell)
237        endc_combo_config['lte_pcc'] = 1
238        lte_cell_count = 1
239        lte_dl_carriers = [1]
240        lte_ul_carriers = [1]
241
242    if 'nr_band' in test_config and test_config['nr_band']:
243        nr_cell = {
244            'cell_type':
245            'NR5G',
246            'cell_number':
247            1,
248            'band':
249            test_config['nr_band'],
250            'nr_cell_type':
251            test_config['nr_cell_type'],
252            'duplex_mode':
253            test_config['nr_duplex_mode'],
254            'dl_mimo_config':
255            'N{nss}X{nss}'.format(nss=test_config['nr_dl_mimo_config']),
256            'dl_bandwidth_class':
257            'A',
258            'dl_bandwidth':
259            test_config['nr_bandwidth'],
260            'ul_enabled':
261            1,
262            'ul_bandwidth_class':
263            'A',
264            'ul_mimo_config':
265            'N{nss}X{nss}'.format(nss=test_config['nr_ul_mimo_config']),
266            'subcarrier_spacing':
267            'MU0' if test_config['nr_scs'] == '15' else 'MU1'
268        }
269        cell_config_list.append(nr_cell)
270        nr_cell_count = 1
271        nr_dl_carriers = [1]
272        nr_ul_carriers = [1]
273
274    endc_combo_config['lte_cell_count'] = lte_cell_count
275    endc_combo_config['nr_cell_count'] = nr_cell_count
276    endc_combo_config['nr_dl_carriers'] = nr_dl_carriers
277    endc_combo_config['nr_ul_carriers'] = nr_ul_carriers
278    endc_combo_config['cell_list'] = cell_config_list
279    endc_combo_config['lte_scc_list'] = lte_scc_list
280    endc_combo_config['lte_dl_carriers'] = lte_dl_carriers
281    endc_combo_config['lte_ul_carriers'] = lte_ul_carriers
282    return endc_combo_config
283
284
285class DeviceUtils():
286
287    def __new__(self, dut, log):
288        if hasattr(dut,
289                   'device_type') and dut.device_type == 'android_non_pixel':
290            return AndroidNonPixelDeviceUtils(dut, log)
291        else:
292            return PixelDeviceUtils(dut, log)
293
294
295class PixelDeviceUtils():
296
297    def __init__(self, dut, log):
298        self.dut = dut
299        self.log = log
300
301    def stop_services(self):
302        """Gracefully stop sl4a before power measurement"""
303        self.dut.stop_services()
304
305    def start_services(self):
306        self.dut.start_services()
307
308    def start_pixel_logger(self):
309        """Function to start pixel logger with default log mask.
310
311        Args:
312            ad: android device on which to start logger
313        """
314
315        try:
316            self.dut.adb.shell(
317                'rm -R /storage/emulated/0/Android/data/com.android.pixellogger/files/logs/logs/'
318            )
319        except:
320            pass
321        self.dut.adb.shell(
322            'am startservice -a com.android.pixellogger.service.logging.LoggingService.ACTION_START_LOGGING'
323        )
324
325    def stop_pixel_logger(self, log_path, tag=None):
326        """Function to stop pixel logger and retrieve logs
327
328        Args:
329            ad: android device on which to start logger
330            log_path: location of saved logs
331        """
332        self.dut.adb.shell(
333            'am startservice -a com.android.pixellogger.service.logging.LoggingService.ACTION_STOP_LOGGING'
334        )
335        logging.info('Waiting for Pixel log file')
336        file_name = None
337        file_size = 0
338        previous_file_size = 0
339        for idx in range(600):
340            try:
341                file = self.dut.adb.shell(
342                    'ls -l /storage/emulated/0/Android/data/com.android.pixellogger/files/logs/logs/'
343                ).split(' ')
344                file_name = file[-1]
345                file_size = file[-4]
346            except:
347                file_name = None
348                file_size = 0
349            if file_name and file_size == previous_file_size:
350                logging.info('Log file found after {}s.'.format(idx))
351                break
352            else:
353                previous_file_size = file_size
354                time.sleep(1)
355        try:
356            local_file_name = '{}_{}'.format(file_name,
357                                             tag) if tag else file_name
358            local_path = os.path.join(log_path, local_file_name)
359            self.dut.pull_files(
360                '/storage/emulated/0/Android/data/com.android.pixellogger/files/logs/logs/{}'
361                .format(file_name), log_path)
362            return local_path
363        except:
364            logging.error('Could not pull pixel logs.')
365
366    def log_system_power_metrics(self, verbose=1):
367        # Log temperature sensors
368        if verbose:
369            temp_sensors = self.dut.adb.shell(
370                'ls -1 /dev/thermal/tz-by-name/').splitlines()
371        else:
372            temp_sensors = ['BIG', 'battery', 'quiet_therm', 'usb_pwr_therm']
373        temp_measurements = collections.OrderedDict()
374        for sensor in temp_sensors:
375            try:
376                temp_measurements[sensor] = self.dut.adb.shell(
377                    'cat /dev/thermal/tz-by-name/{}/temp'.format(sensor))
378            except:
379                temp_measurements[sensor] = float('nan')
380        logging.debug(
381            'Temperature sensor readings: {}'.format(temp_measurements))
382
383        # Log mitigation items
384        if verbose:
385            mitigation_points = [
386                "batoilo",
387                "ocp_cpu1",
388                "ocp_cpu2",
389                "ocp_gpu",
390                "ocp_tpu",
391                "smpl_warn",
392                "soft_ocp_cpu1",
393                "soft_ocp_cpu2",
394                "soft_ocp_gpu",
395                "soft_ocp_tpu",
396                "vdroop1",
397                "vdroop2",
398            ]
399        else:
400            mitigation_points = [
401                "batoilo",
402                "smpl_warn",
403                "vdroop1",
404                "vdroop2",
405            ]
406
407        parameters_f = ['count', 'capacity', 'timestamp', 'voltage']
408        parameters_v = ['count', 'cap', 'time', 'volt']
409        mitigation_measurements = collections.OrderedDict()
410        for mp in mitigation_points:
411            mitigation_measurements[mp] = collections.OrderedDict()
412            for par_f, par_v in zip(parameters_f, parameters_v):
413                mitigation_measurements[mp][par_v] = self.dut.adb.shell(
414                    'cat /sys/devices/virtual/pmic/mitigation/last_triggered_{}/{}_{}'
415                    .format(par_f, mp, par_v))
416        logging.debug(
417            'Mitigation readings: {}'.format(mitigation_measurements))
418
419        # Log power meter items
420        power_meter_measurements = collections.OrderedDict()
421        for device in ['device0', 'device1']:
422            power_str = self.dut.adb.shell(
423                'cat /sys/bus/iio/devices/iio:{}/lpf_power'.format(
424                    device)).splitlines()
425            power_meter_measurements[device] = collections.OrderedDict()
426            for line in power_str:
427                if line.startswith('CH'):
428                    try:
429                        line_split = line.split(', ')
430                        power_meter_measurements[device][line_split[0]] = int(
431                            line_split[1])
432                    except (IndexError, ValueError):
433                        continue
434                elif line.startswith('t='):
435                    try:
436                        power_meter_measurements[device]['t_pmeter'] = int(
437                            line[2:])
438                    except (IndexError, ValueError):
439                        continue
440                else:
441                    continue
442            logging.debug(
443                'Power Meter readings: {}'.format(power_meter_measurements))
444
445            # Log battery items
446            if verbose:
447                battery_parameters = [
448                    "act_impedance", "capacity", "charge_counter",
449                    "charge_full", "charge_full_design", "current_avg",
450                    "current_now", "cycle_count", "health", "offmode_charger",
451                    "present", "rc_switch_enable", "resistance", "status",
452                    "temp", "voltage_avg", "voltage_now", "voltage_ocv"
453                ]
454            else:
455                battery_parameters = [
456                    "capacity", "current_avg", "current_now", "voltage_avg",
457                    "voltage_now", "voltage_ocv"
458                ]
459
460            battery_meaurements = collections.OrderedDict()
461            for par in battery_parameters:
462                battery_meaurements['bat_{}'.format(par)] = self.dut.adb.shell(
463                    'cat /sys/class/power_supply/maxfg/{}'.format(par))
464            logging.debug('Battery readings: {}'.format(battery_meaurements))
465
466    def log_odpm(self, file_path):
467        """Dumpsys ODPM data and save it."""
468        try:
469            stats = self.dut.adb.shell(POWER_STATS_DUMPSYS_CMD)
470            with open(file_path, 'w') as f:
471                f.write(stats)
472        except AdbError as e:
473            self.log.warning('Error dumping and saving odpm')
474
475    def send_at_command(self, at_command):
476        at_cmd_output = self.dut.adb.shell(
477            'am instrument -w -e request {} -e response wait '
478            '"com.google.mdstest/com.google.mdstest.instrument.ModemATCommandInstrumentation"'
479            .format(at_command))
480        return at_cmd_output
481
482    def get_rx_measurements(self, cell_type):
483        cell_type_int = 7 if cell_type == 'LTE' else 8
484        try:
485            rx_meas = self.send_at_command(
486                'AT+GOOGGETRXMEAS\={}?'.format(cell_type_int))
487        except:
488            rx_meas = ''
489        rsrp_regex = r"RSRP\[\d+\]\s+(-?\d+)"
490        rsrp_values = [float(x) for x in re.findall(rsrp_regex, rx_meas)]
491        rsrq_regex = r"RSRQ\[\d+\]\s+(-?\d+)"
492        rsrq_values = [float(x) for x in re.findall(rsrq_regex, rx_meas)]
493        rssi_regex = r"RSSI\[\d+\]\s+(-?\d+)"
494        rssi_values = [float(x) for x in re.findall(rssi_regex, rx_meas)]
495        sinr_regex = r"SINR\[\d+\]\s+(-?\d+)"
496        sinr_values = [float(x) for x in re.findall(sinr_regex, rx_meas)]
497        return {
498            'rsrp': rsrp_values,
499            'rsrq': rsrq_values,
500            'rssi': rssi_values,
501            'sinr': sinr_values
502        }
503
504    def get_fr2_tx_power(self):
505        try:
506            tx_power = self.send_at_command('AT+MMPDREAD=0,2,0')
507        except:
508            tx_power = ''
509        logging.info(tx_power)
510
511    def toggle_airplane_mode(self,
512                             new_state=None,
513                             strict_checking=True,
514                             try_index=0):
515        """ Toggle the state of airplane mode.
516
517        Args:
518            log: log handler.
519            ad: android_device object.
520            new_state: Airplane mode state to set to.
521                If None, opposite of the current state.
522            strict_checking: Whether to turn on strict checking that checks all features.
523            try_index: index of apm toggle
524
525        Returns:
526            result: True if operation succeed. False if error happens.
527        """
528        if hasattr(
529                self.dut, 'toggle_airplane_mode'
530        ) and 'at_command' in self.dut.toggle_airplane_mode['method']:
531            cfun_setting = 0 if new_state else 1
532            self.log.info(
533                'Toggling airplane mode {} by AT command.'.format(new_state))
534            self.send_at_command('AT+CFUN={}'.format(cfun_setting))
535        elif self.dut.skip_sl4a or try_index % 2 == 0:
536            self.log.info(
537                'Toggling airplane mode {} by adb.'.format(new_state))
538            return tel_utils.toggle_airplane_mode_by_adb(
539                self.log, self.dut, new_state)
540        else:
541            self.log.info(
542                'Toggling airplane mode {} by msim.'.format(new_state))
543            return self.toggle_airplane_mode_msim(
544                new_state, strict_checking=strict_checking)
545
546    def toggle_airplane_mode_msim(self, new_state=None, strict_checking=True):
547        """ Toggle the state of airplane mode.
548
549        Args:
550            log: log handler.
551            ad: android_device object.
552            new_state: Airplane mode state to set to.
553                If None, opposite of the current state.
554            strict_checking: Whether to turn on strict checking that checks all features.
555
556        Returns:
557            result: True if operation succeed. False if error happens.
558        """
559
560        cur_state = self.dut.droid.connectivityCheckAirplaneMode()
561        if cur_state == new_state:
562            self.dut.log.info("Airplane mode already in %s", new_state)
563            return True
564        elif new_state is None:
565            new_state = not cur_state
566            self.dut.log.info("Toggle APM mode, from current tate %s to %s",
567                              cur_state, new_state)
568        sub_id_list = []
569        active_sub_info = self.dut.droid.subscriptionGetAllSubInfoList()
570        if active_sub_info:
571            for info in active_sub_info:
572                sub_id_list.append(info['subscriptionId'])
573
574        self.dut.ed.clear_all_events()
575        time.sleep(0.1)
576        service_state_list = []
577        if new_state:
578            service_state_list.append(tel_utils.SERVICE_STATE_POWER_OFF)
579            self.dut.log.info("Turn on airplane mode")
580
581        else:
582            # If either one of these 3 events show up, it should be OK.
583            # Normal SIM, phone in service
584            service_state_list.append(tel_utils.SERVICE_STATE_IN_SERVICE)
585            # NO SIM, or Dead SIM, or no Roaming coverage.
586            service_state_list.append(tel_utils.SERVICE_STATE_OUT_OF_SERVICE)
587            service_state_list.append(tel_utils.SERVICE_STATE_EMERGENCY_ONLY)
588            self.dut.log.info("Turn off airplane mode")
589
590        for sub_id in sub_id_list:
591            self.dut.droid.telephonyStartTrackingServiceStateChangeForSubscription(
592                sub_id)
593
594        timeout_time = time.time() + LONG_SLEEP
595        self.dut.droid.connectivityToggleAirplaneMode(new_state)
596
597        try:
598            try:
599                event = self.dut.ed.wait_for_event(
600                    tel_utils.EVENT_SERVICE_STATE_CHANGED,
601                    tel_utils.is_event_match_for_list,
602                    timeout=LONG_SLEEP,
603                    field=tel_utils.ServiceStateContainer.SERVICE_STATE,
604                    value_list=service_state_list)
605                self.dut.log.info("Got event %s", event)
606            except Empty:
607                self.dut.log.warning(
608                    "Did not get expected service state change to %s",
609                    service_state_list)
610            finally:
611                for sub_id in sub_id_list:
612                    self.dut.droid.telephonyStopTrackingServiceStateChangeForSubscription(
613                        sub_id)
614        except Exception as e:
615            self.dut.log.error(e)
616
617        # APM on (new_state=True) will turn off bluetooth but may not turn it on
618        try:
619            if new_state and not tel_utils._wait_for_bluetooth_in_state(
620                    self.log, self.dut, False, timeout_time - time.time()):
621                self.dut.log.error(
622                    "Failed waiting for bluetooth during airplane mode toggle")
623                if strict_checking: return False
624        except Exception as e:
625            self.dut.log.error("Failed to check bluetooth state due to %s", e)
626            if strict_checking:
627                raise
628
629        # APM on (new_state=True) will turn off wifi but may not turn it on
630        if new_state and not tel_utils._wait_for_wifi_in_state(
631                self.log, self.dut, False, timeout_time - time.time()):
632            self.dut.log.error(
633                "Failed waiting for wifi during airplane mode toggle on")
634            if strict_checking: return False
635
636        if self.dut.droid.connectivityCheckAirplaneMode() != new_state:
637            self.dut.log.error("Set airplane mode to %s failed", new_state)
638            return False
639        return True
640
641
642class AndroidNonPixelDeviceUtils():
643
644    def __init__(self, dut, log):
645        self.dut = dut
646        self.log = log
647        self.set_screen_timeout()
648
649    def start_services(self):
650        self.log.debug('stop_services not supported on non_pixel devices')
651
652    def stop_services(self):
653        self.log.debug('stop_services not supported on non_pixel devices')
654
655    def start_pixel_logger(self):
656        self.log.debug('start_pixel_logger not supported on non_pixel devices')
657
658    def stop_pixel_logger(self, log_path, tag=None):
659        self.log.debug('stop_pixel_logger not supported on non_pixel devices')
660
661    def log_system_power_metrics(self, verbose=1):
662        self.log.debug(
663            'log_system_power_metrics not supported on non_pixel devices')
664
665    def log_odpm(self, file_path):
666        self.log.debug('log_odpm not supported on non_pixel devices')
667
668    def send_at_command(self, at_command):
669        self.log.debug('send_at_command not supported on non_pixel devices')
670
671    def get_rx_measurements(self, cell_type):
672        self.log.debug(
673            'get_rx_measurements not supported on non_pixel devices')
674
675    def get_tx_measurements(self, cell_type):
676        self.log.debug(
677            'get_tx_measurements not supported on non_pixel devices')
678
679    def toggle_airplane_mode(self,
680                             new_state=None,
681                             strict_checking=True,
682                             try_index=0):
683        cur_state = bool(
684            int(self.dut.adb.shell("settings get global airplane_mode_on")))
685        if new_state == cur_state:
686            self.log.info(
687                'Airplane mode already in {} state.'.format(cur_state))
688        else:
689            self.tap_airplane_mode()
690
691    def get_screen_state(self):
692        screen_state_output = self.dut.adb.shell(
693            "dumpsys display | grep 'mScreenState'")
694        if 'ON' in screen_state_output:
695            return 1
696        else:
697            return 0
698
699    def set_screen_state(self, state):
700        curr_state = self.get_screen_state()
701        if state == curr_state:
702            self.log.debug('Screen state already {}'.format(state))
703        elif state == True:
704            self.dut.adb.shell('input keyevent KEYCODE_WAKEUP')
705        elif state == False:
706            self.dut.adb.shell('input keyevent KEYCODE_SLEEP')
707
708    def set_screen_timeout(self, timeout=5):
709        self.dut.adb.shell('settings put system screen_off_timeout {}'.format(
710            timeout * 1000))
711
712    def tap_airplane_mode(self):
713        self.set_screen_state(1)
714        for command in self.dut.toggle_airplane_mode['screen_routine']:
715            self.dut.adb.shell(command)
716            time.sleep(SHORT_SLEEP)
717        self.set_screen_state(0)
718