1# /usr/bin/env python3.4
2#
3# Copyright (C) 2018 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may not
6# use this file except in compliance with the License. You may obtain a copy of
7# 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, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations under
15# the License
16
17import json
18import logging
19import math
20import os
21import re
22import subprocess
23import time
24import xlsxwriter
25
26from acts.controllers.ap_lib import hostapd_config
27from acts.controllers.ap_lib import hostapd_constants
28from acts.controllers.ap_lib import hostapd_security
29from acts.test_utils.bt.bt_constants import \
30    bluetooth_profile_connection_state_changed
31from acts.test_utils.bt.bt_constants import bt_default_timeout
32from acts.test_utils.bt.bt_constants import bt_profile_constants
33from acts.test_utils.bt.bt_constants import bt_profile_states
34from acts.test_utils.bt.bt_constants import bt_scan_mode_types
35from acts.test_utils.bt.bt_gatt_utils import GattTestUtilsError
36from acts.test_utils.bt.bt_gatt_utils import orchestrate_gatt_connection
37from acts.test_utils.bt.bt_test_utils import disable_bluetooth
38from acts.test_utils.bt.bt_test_utils import enable_bluetooth
39from acts.test_utils.bt.bt_test_utils import is_a2dp_src_device_connected
40from acts.test_utils.bt.bt_test_utils import is_a2dp_snk_device_connected
41from acts.test_utils.bt.bt_test_utils import is_hfp_client_device_connected
42from acts.test_utils.bt.bt_test_utils import is_map_mce_device_connected
43from acts.test_utils.bt.bt_test_utils import is_map_mse_device_connected
44from acts.test_utils.car.car_telecom_utils import wait_for_active
45from acts.test_utils.car.car_telecom_utils import wait_for_dialing
46from acts.test_utils.car.car_telecom_utils import wait_for_not_in_call
47from acts.test_utils.car.car_telecom_utils import wait_for_ringing
48from acts.test_utils.tel.tel_test_utils import get_phone_number
49from acts.test_utils.tel.tel_test_utils import hangup_call
50from acts.test_utils.tel.tel_test_utils import initiate_call
51from acts.test_utils.tel.tel_test_utils import run_multithread_func
52from acts.test_utils.tel.tel_test_utils import setup_droid_properties
53from acts.test_utils.tel.tel_test_utils import wait_and_answer_call
54from acts.test_utils.wifi.wifi_test_utils import reset_wifi
55from acts.test_utils.wifi.wifi_test_utils import wifi_connect
56from acts.test_utils.wifi.wifi_test_utils import wifi_test_device_init
57from acts.test_utils.wifi.wifi_test_utils import wifi_toggle_state
58from acts.utils import exe_cmd, create_dir
59
60THROUGHPUT_THRESHOLD = 100
61AP_START_TIME = 10
62DISCOVERY_TIME = 10
63BLUETOOTH_WAIT_TIME = 2
64
65
66def a2dp_dumpsys_parser(file_path):
67    """Convenience function to parse a2dp dumpsys logs.
68
69    Args:
70        file_path: Path of dumpsys logs.
71
72    Returns:
73        True if parsing is successful, False otherwise.
74    """
75    a2dp_dumpsys_info = []
76    with open(file_path) as dumpsys_file:
77        for line in dumpsys_file:
78            if "A2DP State:" in line:
79                a2dp_dumpsys_info.append(line)
80            elif "Counts (max dropped)" not in line and len(
81                    a2dp_dumpsys_info) > 0:
82                a2dp_dumpsys_info.append(line)
83            elif "Counts (max dropped)" in line:
84                a2dp_dumpsys_info = ''.join(a2dp_dumpsys_info)
85                logging.info(a2dp_dumpsys_info)
86                return True
87    logging.error("failed to get A2DP state")
88    return False
89
90
91def connect_ble(pri_ad, sec_ad):
92    """Connect BLE device from DUT.
93
94    Args:
95        pri_ad: An android device object.
96        sec_ad: An android device object.
97
98    Returns:
99        True if successful, otherwise False.
100    """
101    adv_instances = []
102    gatt_server_list = []
103    bluetooth_gatt_list = []
104    pri_ad.droid.bluetoothEnableBLE()
105    gatt_server_cb = sec_ad.droid.gattServerCreateGattServerCallback()
106    gatt_server = sec_ad.droid.gattServerOpenGattServer(gatt_server_cb)
107    gatt_server_list.append(gatt_server)
108    try:
109        bluetooth_gatt, gatt_callback, adv_callback = (
110            orchestrate_gatt_connection(pri_ad, sec_ad))
111        bluetooth_gatt_list.append(bluetooth_gatt)
112    except GattTestUtilsError as err:
113        logging.error(err)
114        return False
115    adv_instances.append(adv_callback)
116    connected_devices = sec_ad.droid.gattServerGetConnectedDevices(gatt_server)
117    logging.debug("Connected device = {}".format(connected_devices))
118    return True
119
120
121def collect_bluetooth_manager_dumpsys_logs(pri_ad):
122    """Collect "adb shell dumpsys bluetooth_manager" logs.
123
124    Args:
125        pri_ad : An android device.
126
127    Returns:
128        True if dumpsys is successful, False otherwise.
129    """
130    out_file = "{}_{}".format(pri_ad.serial, "bluetooth_dumpsys.txt")
131    dumpsys_path = ''.join((pri_ad.log_path, "/BluetoothDumpsys"))
132    create_dir(dumpsys_path)
133    cmd = ''.join("adb -s {} shell dumpsys bluetooth_manager > {}/{}".format(
134        pri_ad.serial, dumpsys_path, out_file))
135    exe_cmd(cmd)
136    file_path = "{}/{}".format(dumpsys_path, out_file)
137    if not a2dp_dumpsys_parser(file_path):
138        logging.error("Could not parse dumpsys logs")
139        return False
140    return True
141
142
143def configure_and_start_ap(ap, network):
144    """Configure hostapd parameters and starts access point.
145
146    Args:
147        ap: An access point object.
148        network: A dictionary with wifi network details.
149    """
150    hostapd_sec = hostapd_security.Security(
151        security_mode=network["security"], password=network["password"])
152
153    config = hostapd_config.HostapdConfig(
154        n_capabilities=[hostapd_constants.N_CAPABILITY_HT40_MINUS],
155        mode=hostapd_constants.MODE_11N_PURE,
156        channel=network["channel"],
157        ssid=network["SSID"],
158        security=hostapd_sec)
159    ap.start_ap(config)
160    time.sleep(AP_START_TIME)
161
162
163def connect_dev_to_headset(pri_droid, dev_to_connect, profiles_set):
164    supported_profiles = bt_profile_constants.values()
165    for profile in profiles_set:
166        if profile not in supported_profiles:
167            pri_droid.log.info("Profile {} is not supported list {}".format(
168                profile, supported_profiles))
169            return False
170
171    paired = False
172    for paired_device in pri_droid.droid.bluetoothGetBondedDevices():
173        if paired_device['address'] == \
174                dev_to_connect:
175            paired = True
176            break
177
178    if not paired:
179        pri_droid.log.info("{} not paired to {}".format(
180            pri_droid.droid.getBuildSerial(), dev_to_connect))
181        return False
182
183    pri_droid.droid.bluetoothConnectBonded(dev_to_connect)
184
185    end_time = time.time() + 10
186    profile_connected = set()
187    sec_addr = dev_to_connect
188    logging.info("Profiles to be connected {}".format(profiles_set))
189
190    while (time.time() < end_time and
191               not profile_connected.issuperset(profiles_set)):
192        if (bt_profile_constants['headset_client'] not in profile_connected and
193                    bt_profile_constants['headset_client'] in profiles_set):
194            if is_hfp_client_device_connected(pri_droid, sec_addr):
195                profile_connected.add(bt_profile_constants['headset_client'])
196        if (bt_profile_constants['headset'] not in profile_connected and
197                    bt_profile_constants['headset'] in profiles_set):
198            profile_connected.add(bt_profile_constants['headset'])
199        if (bt_profile_constants['a2dp'] not in profile_connected and
200                    bt_profile_constants['a2dp'] in profiles_set):
201            if is_a2dp_src_device_connected(pri_droid, sec_addr):
202                profile_connected.add(bt_profile_constants['a2dp'])
203        if (bt_profile_constants['a2dp_sink'] not in profile_connected and
204                    bt_profile_constants['a2dp_sink'] in profiles_set):
205            if is_a2dp_snk_device_connected(pri_droid, sec_addr):
206                profile_connected.add(bt_profile_constants['a2dp_sink'])
207        if (bt_profile_constants['map_mce'] not in profile_connected and
208                    bt_profile_constants['map_mce'] in profiles_set):
209            if is_map_mce_device_connected(pri_droid, sec_addr):
210                profile_connected.add(bt_profile_constants['map_mce'])
211        if (bt_profile_constants['map'] not in profile_connected and
212                    bt_profile_constants['map'] in profiles_set):
213            if is_map_mse_device_connected(pri_droid, sec_addr):
214                profile_connected.add(bt_profile_constants['map'])
215        time.sleep(0.1)
216
217    while not profile_connected.issuperset(profiles_set):
218        try:
219            time.sleep(10)
220            profile_event = pri_droid.ed.pop_event(
221                bluetooth_profile_connection_state_changed,
222                bt_default_timeout + 10)
223            logging.info("Got event {}".format(profile_event))
224        except Exception:
225            logging.error("Did not get {} profiles left {}".format(
226                bluetooth_profile_connection_state_changed, profile_connected))
227            return False
228        profile = profile_event['data']['profile']
229        state = profile_event['data']['state']
230        device_addr = profile_event['data']['addr']
231        if state == bt_profile_states['connected'] and \
232                        device_addr == dev_to_connect:
233            profile_connected.add(profile)
234        logging.info("Profiles connected until now {}".format(
235            profile_connected))
236    return True
237
238
239def device_discoverable(pri_ad, sec_ad):
240    """Verifies whether the device is discoverable or not.
241
242    Args:
243        pri_ad: An primary android device object.
244        sec_ad: An secondary android device object.
245
246    Returns:
247        True if the device found, False otherwise.
248    """
249    pri_ad.droid.bluetoothMakeDiscoverable()
250    scan_mode = pri_ad.droid.bluetoothGetScanMode()
251    if scan_mode == bt_scan_mode_types['connectable_discoverable']:
252        logging.info("Primary device scan mode is "
253                     "SCAN_MODE_CONNECTABLE_DISCOVERABLE.")
254    else:
255        logging.info("Primary device scan mode is not "
256                     "SCAN_MODE_CONNECTABLE_DISCOVERABLE.")
257        return False
258    if sec_ad.droid.bluetoothStartDiscovery():
259        time.sleep(DISCOVERY_TIME)
260        droid_name = pri_ad.droid.bluetoothGetLocalName()
261        get_discovered_devices = sec_ad.droid.bluetoothGetDiscoveredDevices()
262        find_flag = False
263
264        if get_discovered_devices:
265            for device in get_discovered_devices:
266                if 'name' in device and device['name'] == droid_name:
267                    logging.info("Primary device is in the discovery "
268                                 "list of secondary device.")
269                    find_flag = True
270                    break
271        else:
272            logging.info("Secondary device get all the discovered devices "
273                         "list is empty")
274            return False
275    else:
276        logging.info("Secondary device start discovery process error.")
277        return False
278    if not find_flag:
279        return False
280    return True
281
282
283def disconnect_headset_from_dev(pri_ad, sec_ad, profiles_list):
284    """
285    Disconnect primary from secondary on a specific set of profiles
286    Args:
287        pri_ad - Primary android_device initiating disconnection
288        sec_ad - Secondary android droid (sl4a interface to keep the
289          method signature the same connect_pri_to_sec above)
290        profiles_list - List of profiles we want to disconnect from
291
292    Returns:
293        True on Success
294        False on Failure
295    """
296    supported_profiles = bt_profile_constants.values()
297    for profile in profiles_list:
298        if profile not in supported_profiles:
299            pri_ad.log.info("Profile {} is not in supported list {}".format(
300                profile, supported_profiles))
301            return False
302
303    pri_ad.log.info(pri_ad.droid.bluetoothGetBondedDevices())
304
305    try:
306        pri_ad.droid.bluetoothDisconnectConnectedProfile(sec_ad, profiles_list)
307    except Exception as err:
308        pri_ad.log.error(
309            "Exception while trying to disconnect profile(s) {}: {}".format(
310                profiles_list, err))
311        return False
312
313    profile_disconnected = set()
314    pri_ad.log.info("Disconnecting from profiles: {}".format(profiles_list))
315
316    while not profile_disconnected.issuperset(profiles_list):
317        try:
318            profile_event = pri_ad.ed.pop_event(
319                bluetooth_profile_connection_state_changed, bt_default_timeout)
320            pri_ad.log.info("Got event {}".format(profile_event))
321        except Exception:
322            pri_ad.log.error("Did not disconnect from Profiles")
323            return False
324
325        profile = profile_event['data']['profile']
326        state = profile_event['data']['state']
327        device_addr = profile_event['data']['addr']
328
329        if state == bt_profile_states['disconnected'] and \
330                        device_addr == sec_ad:
331            profile_disconnected.add(profile)
332        pri_ad.log.info("Profiles disconnected so far {}".format(
333            profile_disconnected))
334
335    return True
336
337
338def initiate_disconnect_from_hf(audio_receiver, pri_ad, sec_ad, duration):
339    """Initiates call and disconnect call on primary device.
340    Steps:
341    1. Initiate call from HF.
342    2. Wait for dialing state at DUT and wait for ringing at secondary device.
343    3. Accepts call from secondary device.
344    4. Wait for call active state at primary and secondary device.
345    5. Sleeps until given duration.
346    6. Disconnect call from primary device.
347    7. Wait for call is not present state.
348
349    Args:
350        pri_ad: An android device to disconnect call.
351        sec_ad: An android device accepting call.
352        duration: Duration of call in seconds.
353
354    Returns:
355        True if successful, False otherwise.
356    """
357    audio_receiver.initiate_call_from_hf()
358    time.sleep(2)
359    flag = True
360    flag &= wait_for_dialing(logging, pri_ad)
361    flag &= wait_for_ringing(logging, sec_ad)
362    if not flag:
363        logging.error("Outgoing call did not get established")
364        return False
365
366    if not wait_and_answer_call(logging, sec_ad):
367        logging.error("Failed to answer call in second device.")
368        return False
369    if not wait_for_active(logging, pri_ad):
370        logging.error("AG not in Active state.")
371        return False
372    if not wait_for_active(logging, sec_ad):
373        logging.error("RE not in Active state.")
374        return False
375    time.sleep(duration)
376    if not hangup_call(logging, pri_ad):
377        logging.error("Failed to hangup call.")
378        return False
379    flag = True
380    flag &= wait_for_not_in_call(logging, pri_ad)
381    flag &= wait_for_not_in_call(logging, sec_ad)
382    return flag
383
384
385def initiate_disconnect_call_dut(pri_ad, sec_ad, duration, callee_number):
386    """Initiates call and disconnect call on primary device.
387    Steps:
388    1. Initiate call from DUT.
389    2. Wait for dialing state at DUT and wait for ringing at secondary device.
390    3. Accepts call from secondary device.
391    4. Wait for call active state at primary and secondary device.
392    5. Sleeps until given duration.
393    6. Disconnect call from primary device.
394    7. Wait for call is not present state.
395
396    Args:
397        pri_ad: An android device to disconnect call.
398        sec_ad: An android device accepting call.
399        duration: Duration of call in seconds.
400        callee_number: Secondary device's phone number.
401
402    Returns:
403        True if successful, False otherwise.
404    """
405    if not initiate_call(logging, pri_ad, callee_number):
406        logging.error("Failed to initiate call")
407        return False
408    time.sleep(2)
409
410    flag = True
411    flag &= wait_for_dialing(logging, pri_ad)
412    flag &= wait_for_ringing(logging, sec_ad)
413    if not flag:
414        logging.error("Outgoing call did not get established")
415        return False
416
417    if not wait_and_answer_call(logging, sec_ad):
418        logging.error("Failed to answer call in second device.")
419        return False
420    # Wait for AG, RE to go into an Active state.
421    if not wait_for_active(logging, pri_ad):
422        logging.error("AG not in Active state.")
423        return False
424    if not wait_for_active(logging, sec_ad):
425        logging.error("RE not in Active state.")
426        return False
427    time.sleep(duration)
428    if not hangup_call(logging, pri_ad):
429        logging.error("Failed to hangup call.")
430        return False
431    flag = True
432    flag &= wait_for_not_in_call(logging, pri_ad)
433    flag &= wait_for_not_in_call(logging, sec_ad)
434
435    return flag
436
437
438def iperf_result(result, stream):
439    """Accepts the iperf result in json format and parse the output to
440    get throughput value.
441
442    Args:
443        result: contains the logs of iperf in json format.
444        stream: string to indicate uplink/downlink traffic.
445
446    Returns:
447        tx_rate: Data sent from client.
448        rx_rate: Data received from client.
449    """
450    try:
451        with open(result, 'r') as iperf_data:
452            time.sleep(1)
453            json_data = json.load(iperf_data)
454    except ValueError:
455        with open(result, 'r') as iperf_data:
456            # Possibly a result from interrupted iperf run, skip first line
457            # and try again.
458            time.sleep(1)
459            lines = iperf_data.readlines()[1:]
460            json_data = json.loads(''.join(lines))
461
462    if "error" in json_data:
463        logging.error(json_data["error"])
464        return False
465
466    protocol = json_data["start"]["test_start"]["protocol"]
467    if protocol == "UDP":
468        if "intervals" in json_data.keys():
469            interval = [
470                interval["sum"]["bits_per_second"] / 1e6
471                for interval in json_data["intervals"]
472            ]
473            tx_rate = math.fsum(interval) / len(interval)
474        else:
475            logging.error("Unable to retrive client results")
476            return False
477        if "server_output_json" in json_data.keys():
478            interval = [
479                interval["sum"]["bits_per_second"] / 1e6
480                for interval in json_data["server_output_json"]["intervals"]
481            ]
482            rx_rate = math.fsum(interval) / len(interval)
483        else:
484            logging.info("unable to retrive server results")
485            return False
486        if not stream == "ul":
487            return tx_rate, rx_rate
488        return rx_rate, tx_rate
489
490    elif protocol == "TCP":
491        tx_rate = json_data['end']['sum_sent']['bits_per_second']
492        rx_rate = json_data['end']['sum_received']['bits_per_second']
493        return tx_rate / 1e6, rx_rate / 1e6
494    else:
495        return False
496
497
498def is_a2dp_connected(pri_ad, headset_mac_address):
499    """Convenience Function to see if the 2 devices are connected on A2DP.
500
501    Args:
502        pri_ad : An android device.
503        headset_mac_address : Mac address of headset.
504
505    Returns:
506        True:If A2DP connection exists, False otherwise.
507    """
508    devices = pri_ad.droid.bluetoothA2dpGetConnectedDevices()
509    for device in devices:
510        logging.debug("A2dp Connected device {}".format(device["name"]))
511        if device["address"] == headset_mac_address:
512            return True
513    return False
514
515
516def media_stream_check(pri_ad, duration, headset_mac_address):
517    """Checks whether A2DP connecion is active or not for given duration of
518    time.
519
520    Args:
521        pri_ad : An android device.
522        duration : No of seconds to check if a2dp streaming is alive.
523        headset_mac_address : Headset mac address.
524
525    Returns:
526        True: If A2dp connection is active for entire duration.
527        False: If A2dp connection is not active.
528    """
529    while time.time() < duration:
530        if not is_a2dp_connected(pri_ad, headset_mac_address):
531            logging.error("A2dp connection not active at {}".format(
532                pri_ad.droid.getBuildSerial()))
533            return False
534        time.sleep(1)
535    return True
536
537
538def multithread_func(log, tasks):
539    """Multi-thread function wrapper.
540
541    Args:
542        log: log object.
543        tasks: tasks to be executed in parallel.
544
545    Returns:
546       List of results of tasks
547    """
548    results = run_multithread_func(log, tasks)
549    for res in results:
550        if not res:
551            return False
552    return True
553
554
555def music_play_and_check(pri_ad, headset_mac_address, music_to_play, duration):
556    """Starts playing media and checks if media plays for n seconds.
557
558    Steps:
559    1. Starts media player on android device.
560    2. Checks if music streaming is ongoing for n seconds.
561    3. Stops media player.
562    4. Collect dumpsys logs.
563
564    Args:
565        pri_ad: An android device.
566        headset_mac_address: Mac address of third party headset.
567        music_to_play: Indicates the music file to play.
568        duration: Time in secs to indicate music time to play.
569
570    Returns:
571        True if successful, False otherwise.
572    """
573    if not start_media_play(pri_ad, music_to_play):
574        logging.error("Start media play failed.")
575        return False
576    stream_time = time.time() + duration
577    if not media_stream_check(pri_ad, stream_time, headset_mac_address):
578        logging.error("A2DP Connection check failed.")
579        pri_ad.droid.mediaPlayStop()
580        return False
581    pri_ad.droid.mediaPlayStop()
582    if not collect_bluetooth_manager_dumpsys_logs(pri_ad):
583        return False
584    return True
585
586
587def music_play_and_check_via_app(pri_ad, headset_mac_address):
588    """Starts google music player and check for A2DP connection.
589
590    Steps:
591    1. Starts Google music player on android device.
592    2. Checks for A2DP connection.
593
594    Args:
595        pri_ad: An android device.
596        headset_mac_address: Mac address of third party headset.
597
598    Returns:
599        True if successful, False otherwise.
600    """
601    pri_ad.adb.shell("am start com.google.android.music")
602    time.sleep(3)
603    pri_ad.adb.shell("input keyevent 85")
604
605    if not is_a2dp_connected(pri_ad, headset_mac_address):
606        logging.error("A2dp connection not active at {}".format(
607            pri_ad.droid.getBuildSerial()))
608        return False
609    return True
610
611
612def get_phone_ip(ad):
613    """Get the WiFi IP address of the phone.
614
615    Args:
616        ad: the android device under test
617
618    Returns:
619        Ip address of the phone for WiFi, as a string
620    """
621    return ad.droid.connectivityGetIPv4Addresses('wlan0')[0]
622
623
624def pair_dev_to_headset(pri_ad, dev_to_pair):
625
626    bonded_devices = pri_ad.droid.bluetoothGetBondedDevices()
627    for d in bonded_devices:
628        if d['address'] == dev_to_pair:
629            pri_ad.log.info("Successfully bonded to device".format(
630                dev_to_pair))
631            return True
632    pri_ad.droid.bluetoothStartDiscovery()
633    time.sleep(10)
634    pri_ad.droid.bluetoothCancelDiscovery()
635    logging.info("disovered devices = {}".format(
636        pri_ad.droid.bluetoothGetDiscoveredDevices()))
637    for device in pri_ad.droid.bluetoothGetDiscoveredDevices():
638        if device['address'] == dev_to_pair:
639
640            result = pri_ad.droid.bluetoothDiscoverAndBond(dev_to_pair)
641            pri_ad.log.info(result)
642            end_time = time.time() + bt_default_timeout
643            pri_ad.log.info("Verifying devices are bonded")
644            time.sleep(5)
645            while time.time() < end_time:
646                bonded_devices = pri_ad.droid.bluetoothGetBondedDevices()
647                bonded = False
648                for d in bonded_devices:
649                    if d['address'] == dev_to_pair:
650                        pri_ad.log.info("Successfully bonded to device".format(
651                            dev_to_pair))
652                        return True
653    pri_ad.log.info("Failed to bond devices.")
654    return False
655
656def pair_and_connect_headset(pri_ad, headset_mac_address, profile_to_connect):
657    """Pair and connect android device with third party headset.
658
659    Args:
660        pri_ad: An android device.
661        headset_mac_address: Mac address of third party headset.
662        profile_to_connect: Profile to be connected with headset.
663
664    Returns:
665        True if pair and connect to headset successful, False otherwise.
666    """
667    if not pair_dev_to_headset(pri_ad, headset_mac_address):
668        logging.error("Could not pair to headset.")
669        return False
670    # Wait until pairing gets over.
671    time.sleep(2)
672    if not connect_dev_to_headset(pri_ad, headset_mac_address,
673                                  profile_to_connect):
674        logging.error("Could not connect to headset.")
675        return False
676    return True
677
678
679def perform_classic_discovery(pri_ad):
680    """Convenience function to start and stop device discovery.
681
682    Args:
683        pri_ad: An android device.
684
685    Returns:
686        True start and stop discovery is successful, False otherwise.
687    """
688    if not pri_ad.droid.bluetoothStartDiscovery():
689        logging.info("Failed to start inquiry")
690        return False
691    time.sleep(DISCOVERY_TIME)
692    if not pri_ad.droid.bluetoothCancelDiscovery():
693        logging.info("Failed to cancel inquiry")
694        return False
695    logging.info("Discovered device list {}".format(
696        pri_ad.droid.bluetoothGetDiscoveredDevices()))
697    return True
698
699
700def connect_wlan_profile(pri_ad, network):
701    """Disconnect and Connect to AP.
702
703    Args:
704        pri_ad: An android device.
705        network: Network to which AP to be connected.
706
707    Returns:
708        True if successful, False otherwise.
709    """
710    reset_wifi(pri_ad)
711    wifi_toggle_state(pri_ad, False)
712    wifi_test_device_init(pri_ad)
713    wifi_connect(pri_ad, network)
714    if not wifi_connection_check(pri_ad, network["SSID"]):
715        logging.error("Wifi connection does not exist.")
716        return False
717    return True
718
719
720def toggle_bluetooth(pri_ad, iterations):
721    """Toggles bluetooth on/off for N iterations.
722
723    Args:
724        pri_ad: An android device object.
725        iterations: Number of iterations to run.
726
727    Returns:
728        True if successful, False otherwise.
729    """
730    for i in range(iterations):
731        logging.info("Bluetooth Turn on/off iteration : {}".format(i))
732        if not enable_bluetooth(pri_ad.droid, pri_ad.ed):
733            logging.error("Failed to enable bluetooth")
734            return False
735        time.sleep(BLUETOOTH_WAIT_TIME)
736        if not disable_bluetooth(pri_ad.droid):
737            logging.error("Failed to turn off bluetooth")
738            return False
739        time.sleep(BLUETOOTH_WAIT_TIME)
740    return True
741
742
743def toggle_screen_state(pri_ad, iterations):
744    """Toggles the screen state to on or off..
745
746    Args:
747        pri_ad: Android device.
748        iterations: Number of times screen on/off should be performed.
749
750    Returns:
751        True if successful, False otherwise.
752    """
753    for i in range(iterations):
754        if not pri_ad.ensure_screen_on():
755            logging.error("User window cannot come up")
756            return False
757        if not pri_ad.go_to_sleep():
758            logging.info("Screen off")
759    return True
760
761
762def setup_tel_config(pri_ad, sec_ad, sim_conf_file):
763    """Sets tel properties for primary device and secondary devices
764
765    Args:
766        pri_ad: An android device object.
767        sec_ad: An android device object.
768        sim_conf_file: Sim card map.
769
770    Returns:
771        pri_ad_num: Phone number of primary device.
772        sec_ad_num: Phone number of secondary device.
773    """
774    setup_droid_properties(logging, pri_ad, sim_conf_file)
775    pri_ad_num = get_phone_number(logging, pri_ad)
776    setup_droid_properties(logging, sec_ad, sim_conf_file)
777    sec_ad_num = get_phone_number(logging, sec_ad)
778    return pri_ad_num, sec_ad_num
779
780
781def start_fping(pri_ad, duration):
782    """Starts fping to ping for DUT's ip address.
783
784    Steps:
785    1. Run fping command to check DUT's IP is alive or not.
786
787    Args:
788        pri_ad: An android device object.
789        duration: Duration of fping in seconds.
790
791    Returns:
792        True if successful, False otherwise.
793    """
794    out_file_name = "{}".format("fping.txt")
795    full_out_path = os.path.join(pri_ad.log_path, out_file_name)
796    cmd = "fping {} -D -c {}".format(get_phone_ip(pri_ad), duration)
797    cmd = cmd.split()
798    with open(full_out_path, "w") as f:
799        subprocess.call(cmd, stdout=f)
800    f = open(full_out_path, "r")
801    for lines in f:
802        l = re.split("[:/=]", lines)
803        if l[len(l) - 1] != "0%":
804            logging.error("Packet drop observed")
805            return False
806    return True
807
808
809def start_media_play(pri_ad, music_file_to_play):
810    """Starts media player on device.
811
812    Args:
813        pri_ad : An android device.
814        music_file_to_play : An audio file to play.
815
816    Returns:
817        True:If media player start music, False otherwise.
818    """
819    if not pri_ad.droid.mediaPlayOpen("file:///sdcard/Music/{}"
820                                      .format(music_file_to_play)):
821        logging.error("Failed to play music")
822        return False
823
824    pri_ad.droid.mediaPlaySetLooping(True)
825    logging.info("Music is now playing on device {}".format(pri_ad.serial))
826    return True
827
828
829def throughput_pass_fail_check(throughput):
830    """Method to check if the throughput is above the defined
831    threshold.
832
833    Args:
834        throughput: Throughput value of test run.
835
836    Returns:
837        Pass if throughput is above threshold.
838        Fail if throughput is below threshold and Iperf Failed.
839        Empty if Iperf value is not present.
840    """
841    iperf_result_list = []
842    if len(throughput) != 0:
843        for i in range(len(throughput)):
844            if throughput[i] != 'Iperf Failed':
845                if float(throughput[i].split('Mb/s')[0]) > \
846                        THROUGHPUT_THRESHOLD:
847                    iperf_result_list.append("PASS")
848                else:
849                    iperf_result_list.append("FAIL")
850            elif throughput[i] == 'Iperf Failed':
851                iperf_result_list.append("FAIL")
852        return "FAIL" if "FAIL" or "Iperf Failed" in iperf_result_list \
853            else "PASS"
854    else:
855        return " "
856
857
858def wifi_connection_check(pri_ad, ssid):
859    """Function to check existence of wifi connection.
860
861    Args:
862        pri_ad : An android device.
863        ssid : wifi ssid to check.
864
865    Returns:
866        True if wifi connection exists, False otherwise.
867    """
868    wifi_info = pri_ad.droid.wifiGetConnectionInfo()
869    if (wifi_info["SSID"] == ssid and
870            wifi_info["supplicant_state"] == "completed"):
871        return True
872    logging.error("Wifi Connection check failed : {}".format(wifi_info))
873    return False
874
875
876def xlsheet(pri_ad, test_result, attenuation_range=range(0, 1, 1)):
877    """Parses the output and writes in spreadsheet.
878
879    Args:
880        pri_ad: An android device.
881        test_result: final output of testrun.
882        attenuation_range: list of attenuation values.
883    """
884    r = 0
885    c = 0
886    i = 0
887    test_result = json.loads(test_result)
888    file_name = '/'.join((pri_ad.log_path,
889                          test_result["Results"][0]["Test Class"] + ".xlsx"))
890    workbook = xlsxwriter.Workbook(file_name)
891    worksheet = workbook.add_worksheet()
892    wb_format = workbook.add_format()
893    wb_format.set_text_wrap()
894    worksheet.set_column('A:A', 50)
895    worksheet.set_column('B:B', 10)
896    wb_format = workbook.add_format({'bold': True})
897    worksheet.write(r, c, "Test_case_name", wb_format)
898    worksheet.write(r, c + 1, "Result", wb_format)
899    if len(attenuation_range) > 1:
900        for idx in attenuation_range:
901            c += 1
902            worksheet.write(r, c + 1, str(idx) + "db", wb_format)
903    else:
904        worksheet.write(r, c + 2, "Iperf_throughput", wb_format)
905        c += 1
906
907    worksheet.write(r, (c + 2), "Throughput_Result", wb_format)
908    result = [(i["Test Name"], i["Result"], (i["Extras"]),
909               throughput_pass_fail_check(i["Extras"]))
910              for i in test_result["Results"]]
911
912    for row, line in enumerate(result):
913        for col, cell in enumerate(line):
914            if isinstance(cell, list):
915                for i in range(len(cell)):
916                    worksheet.write(row + 1, col, cell[i])
917                    col += 1
918            else:
919                worksheet.write(row + 1, col + i, cell)
920