• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
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
24
25from acts import asserts
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.controllers.utils_lib.ssh import connection
30from acts.controllers.utils_lib.ssh import settings
31from acts.controllers.iperf_server import IPerfResult
32from acts.test_utils.bt import BtEnum
33from acts.test_utils.bt.bt_constants import (
34    bluetooth_profile_connection_state_changed)
35from acts.test_utils.bt.bt_constants import bt_default_timeout
36from acts.test_utils.bt.bt_constants import bt_profile_constants
37from acts.test_utils.bt.bt_constants import bt_profile_states
38from acts.test_utils.bt.bt_constants import bt_scan_mode_types
39from acts.test_utils.bt.bt_gatt_utils import GattTestUtilsError
40from acts.test_utils.bt.bt_gatt_utils import orchestrate_gatt_connection
41from acts.test_utils.bt.bt_test_utils import disable_bluetooth
42from acts.test_utils.bt.bt_test_utils import enable_bluetooth
43from acts.test_utils.bt.bt_test_utils import is_a2dp_src_device_connected
44from acts.test_utils.bt.bt_test_utils import is_a2dp_snk_device_connected
45from acts.test_utils.bt.bt_test_utils import is_hfp_client_device_connected
46from acts.test_utils.bt.bt_test_utils import is_map_mce_device_connected
47from acts.test_utils.bt.bt_test_utils import is_map_mse_device_connected
48from acts.test_utils.bt.bt_test_utils import set_bt_scan_mode
49from acts.test_utils.car.car_telecom_utils import wait_for_active
50from acts.test_utils.car.car_telecom_utils import wait_for_dialing
51from acts.test_utils.car.car_telecom_utils import wait_for_not_in_call
52from acts.test_utils.car.car_telecom_utils import wait_for_ringing
53from acts.test_utils.tel.tel_test_utils import get_phone_number
54from acts.test_utils.tel.tel_test_utils import hangup_call
55from acts.test_utils.tel.tel_test_utils import initiate_call
56from acts.test_utils.tel.tel_test_utils import run_multithread_func
57from acts.test_utils.tel.tel_test_utils import setup_droid_properties
58from acts.test_utils.tel.tel_test_utils import wait_and_answer_call
59from acts.test_utils.wifi.wifi_power_test_utils import bokeh_plot
60from acts.test_utils.wifi.wifi_test_utils import reset_wifi
61from acts.test_utils.wifi.wifi_test_utils import wifi_connect
62from acts.test_utils.wifi.wifi_test_utils import wifi_test_device_init
63from acts.test_utils.wifi.wifi_test_utils import wifi_toggle_state
64from acts.utils import exe_cmd, create_dir
65from bokeh.layouts import column
66from bokeh.models import tools as bokeh_tools
67from bokeh.plotting import figure, output_file, save
68
69THROUGHPUT_THRESHOLD = 100
70AP_START_TIME = 10
71DISCOVERY_TIME = 10
72BLUETOOTH_WAIT_TIME = 2
73AVRCP_WAIT_TIME = 3
74
75
76def avrcp_actions(pri_ad, audio_receiver):
77    """Performs avrcp controls like volume up, volume down, skip next and
78    skip previous.
79
80    Args:
81        pri_ad: Android device.
82        audio_receiver: Relay instance.
83
84    Returns:
85        True if successful, otherwise False.
86    """
87    if "Volume_up" and "Volume_down" in (audio_receiver.relays.keys()):
88        current_volume = pri_ad.droid.getMediaVolume()
89        audio_receiver.press_volume_up()
90        time.sleep(AVRCP_WAIT_TIME)
91        if current_volume == pri_ad.droid.getMediaVolume():
92            pri_ad.log.error("Increase volume failed")
93            return False
94        time.sleep(AVRCP_WAIT_TIME)
95        current_volume = pri_ad.droid.getMediaVolume()
96        audio_receiver.press_volume_down()
97        time.sleep(AVRCP_WAIT_TIME)
98        if current_volume == pri_ad.droid.getMediaVolume():
99            pri_ad.log.error("Decrease volume failed")
100            return False
101    else:
102        pri_ad.log.warning("No volume control pins specfied in relay config.")
103
104    if "Next" and "Previous" in audio_receiver.relays.keys():
105        audio_receiver.press_next()
106        time.sleep(AVRCP_WAIT_TIME)
107        audio_receiver.press_previous()
108        time.sleep(AVRCP_WAIT_TIME)
109    else:
110        pri_ad.log.warning("No track change pins specfied in relay config.")
111    return True
112
113
114def connect_ble(pri_ad, sec_ad):
115    """Connect BLE device from DUT.
116
117    Args:
118        pri_ad: An android device object.
119        sec_ad: An android device object.
120
121    Returns:
122        True if successful, otherwise False.
123    """
124    adv_instances = []
125    gatt_server_list = []
126    bluetooth_gatt_list = []
127    pri_ad.droid.bluetoothEnableBLE()
128    sec_ad.droid.bluetoothEnableBLE()
129    gatt_server_cb = sec_ad.droid.gattServerCreateGattServerCallback()
130    gatt_server = sec_ad.droid.gattServerOpenGattServer(gatt_server_cb)
131    gatt_server_list.append(gatt_server)
132    try:
133        bluetooth_gatt, gatt_callback, adv_callback = (
134            orchestrate_gatt_connection(pri_ad, sec_ad))
135        bluetooth_gatt_list.append(bluetooth_gatt)
136    except GattTestUtilsError as err:
137        pri_ad.log.error(err)
138        return False
139    adv_instances.append(adv_callback)
140    connected_devices = sec_ad.droid.gattServerGetConnectedDevices(gatt_server)
141    pri_ad.log.debug("Connected device = {}".format(connected_devices))
142    return True
143
144
145def collect_bluetooth_manager_dumpsys_logs(pri_ad, test_name):
146    """Collect "adb shell dumpsys bluetooth_manager" logs.
147
148    Args:
149        pri_ad: An android device.
150        test_name: Current test case name.
151
152    Returns:
153        Dumpsys file path.
154    """
155    dump_counter = 0
156    dumpsys_path = os.path.join(pri_ad.log_path, test_name, "BluetoothDumpsys")
157    create_dir(dumpsys_path)
158    while os.path.exists(
159            os.path.join(dumpsys_path,
160                         "bluetooth_dumpsys_%s.txt" % dump_counter)):
161        dump_counter += 1
162    out_file = "bluetooth_dumpsys_%s.txt" % dump_counter
163    cmd = "adb -s {} shell dumpsys bluetooth_manager > {}/{}".format(
164        pri_ad.serial, dumpsys_path, out_file)
165    exe_cmd(cmd)
166    file_path = os.path.join(dumpsys_path, out_file)
167    return file_path
168
169
170def configure_and_start_ap(ap, network):
171    """Configure hostapd parameters and starts access point.
172
173    Args:
174        ap: An access point object.
175        network: A dictionary with wifi network details.
176    """
177    hostapd_sec = None
178    if network["security"] == "wpa2":
179        hostapd_sec = hostapd_security.Security(
180            security_mode=network["security"], password=network["password"])
181
182    config = hostapd_config.HostapdConfig(
183        n_capabilities=[hostapd_constants.N_CAPABILITY_HT40_MINUS],
184        mode=hostapd_constants.MODE_11N_PURE,
185        channel=network["channel"],
186        ssid=network["SSID"],
187        security=hostapd_sec)
188    ap.start_ap(config)
189    time.sleep(AP_START_TIME)
190
191
192def connect_dev_to_headset(pri_droid, dev_to_connect, profiles_set):
193    """Connects primary android device to headset.
194
195    Args:
196        pri_droid: Android device initiating connection.
197        dev_to_connect: Third party headset mac address.
198        profiles_set: Profiles to be connected.
199
200    Returns:
201        True if Pass
202        False if Fail
203    """
204    supported_profiles = bt_profile_constants.values()
205    for profile in profiles_set:
206        if profile not in supported_profiles:
207            pri_droid.log.info("Profile {} is not supported list {}".format(
208                profile, supported_profiles))
209            return False
210
211    paired = False
212    for paired_device in pri_droid.droid.bluetoothGetBondedDevices():
213        if paired_device['address'] == dev_to_connect:
214            paired = True
215            break
216
217    if not paired:
218        pri_droid.log.info("{} not paired to {}".format(pri_droid.serial,
219                                                        dev_to_connect))
220        return False
221
222    end_time = time.time() + 10
223    profile_connected = set()
224    sec_addr = dev_to_connect
225    pri_droid.log.info("Profiles to be connected {}".format(profiles_set))
226
227    while (time.time() < end_time and
228           not profile_connected.issuperset(profiles_set)):
229        if (bt_profile_constants['headset_client'] not in profile_connected and
230                bt_profile_constants['headset_client'] in profiles_set):
231            if is_hfp_client_device_connected(pri_droid, sec_addr):
232                profile_connected.add(bt_profile_constants['headset_client'])
233        if (bt_profile_constants['headset'] not in profile_connected and
234                bt_profile_constants['headset'] in profiles_set):
235            profile_connected.add(bt_profile_constants['headset'])
236        if (bt_profile_constants['a2dp'] not in profile_connected and
237                bt_profile_constants['a2dp'] in profiles_set):
238            if is_a2dp_src_device_connected(pri_droid, sec_addr):
239                profile_connected.add(bt_profile_constants['a2dp'])
240        if (bt_profile_constants['a2dp_sink'] not in profile_connected and
241                bt_profile_constants['a2dp_sink'] in profiles_set):
242            if is_a2dp_snk_device_connected(pri_droid, sec_addr):
243                profile_connected.add(bt_profile_constants['a2dp_sink'])
244        if (bt_profile_constants['map_mce'] not in profile_connected and
245                bt_profile_constants['map_mce'] in profiles_set):
246            if is_map_mce_device_connected(pri_droid, sec_addr):
247                profile_connected.add(bt_profile_constants['map_mce'])
248        if (bt_profile_constants['map'] not in profile_connected and
249                bt_profile_constants['map'] in profiles_set):
250            if is_map_mse_device_connected(pri_droid, sec_addr):
251                profile_connected.add(bt_profile_constants['map'])
252        time.sleep(0.1)
253
254    while not profile_connected.issuperset(profiles_set):
255        try:
256            time.sleep(10)
257            profile_event = pri_droid.ed.pop_event(
258                bluetooth_profile_connection_state_changed,
259                bt_default_timeout + 10)
260            pri_droid.log.info("Got event {}".format(profile_event))
261        except Exception:
262            pri_droid.log.error("Did not get {} profiles left {}".format(
263                bluetooth_profile_connection_state_changed, profile_connected))
264            return False
265        profile = profile_event['data']['profile']
266        state = profile_event['data']['state']
267        device_addr = profile_event['data']['addr']
268        if state == bt_profile_states['connected'] and (
269                device_addr == dev_to_connect):
270            profile_connected.add(profile)
271        pri_droid.log.info(
272            "Profiles connected until now {}".format(profile_connected))
273    return True
274
275
276def device_discoverable(pri_ad, sec_ad):
277    """Verifies whether the device is discoverable or not.
278
279    Args:
280        pri_ad: An primary android device object.
281        sec_ad: An secondary android device object.
282
283    Returns:
284        True if the device found, False otherwise.
285    """
286    pri_ad.droid.bluetoothMakeDiscoverable()
287    scan_mode = pri_ad.droid.bluetoothGetScanMode()
288    if scan_mode == bt_scan_mode_types['connectable_discoverable']:
289        pri_ad.log.info("Primary device scan mode is "
290                        "SCAN_MODE_CONNECTABLE_DISCOVERABLE.")
291    else:
292        pri_ad.log.info("Primary device scan mode is not "
293                        "SCAN_MODE_CONNECTABLE_DISCOVERABLE.")
294        return False
295    if sec_ad.droid.bluetoothStartDiscovery():
296        time.sleep(DISCOVERY_TIME)
297        droid_name = pri_ad.droid.bluetoothGetLocalName()
298        droid_address = pri_ad.droid.bluetoothGetLocalAddress()
299        get_discovered_devices = sec_ad.droid.bluetoothGetDiscoveredDevices()
300        find_flag = False
301
302        if get_discovered_devices:
303            for device in get_discovered_devices:
304                if 'name' in device and device['name'] == droid_name or (
305                        'address' in device and
306                        device["address"] == droid_address):
307                    pri_ad.log.info("Primary device is in the discovery "
308                                    "list of secondary device.")
309                    find_flag = True
310                    break
311        else:
312            pri_ad.log.info("Secondary device get all the discovered devices "
313                            "list is empty")
314            return False
315    else:
316        pri_ad.log.info("Secondary device start discovery process error.")
317        return False
318    if not find_flag:
319        return False
320    return True
321
322
323def device_discoverability(required_devices):
324    """Wrapper function to keep required_devices in discoverable mode.
325
326    Args:
327        required_devices: List of devices to be discovered.
328
329    Returns:
330        discovered_devices: List of BD_ADDR of devices in discoverable mode.
331    """
332    discovered_devices = []
333    if "AndroidDevice" in required_devices:
334        discovered_devices.extend(
335            android_device_discoverability(required_devices["AndroidDevice"]))
336    if "RelayDevice" in required_devices:
337        discovered_devices.extend(
338            relay_device_discoverability(required_devices["RelayDevice"]))
339    return discovered_devices
340
341
342def android_device_discoverability(droid_dev):
343    """To keep android devices in discoverable mode.
344
345    Args:
346        droid_dev: Android device object.
347
348    Returns:
349        device_list: List of device discovered.
350    """
351    device_list = []
352    for device in range(len(droid_dev)):
353        inquiry_device = droid_dev[device]
354        if enable_bluetooth(inquiry_device.droid, inquiry_device.ed):
355            if set_bt_scan_mode(inquiry_device,
356                                bt_scan_mode_types['connectable_discoverable']):
357                device_list.append(
358                    inquiry_device.droid.bluetoothGetLocalAddress())
359            else:
360                droid_dev.log.error(
361                    "Device {} scan mode is not in"
362                    "SCAN_MODE_CONNECTABLE_DISCOVERABLE.".format(
363                        inquiry_device.droid.bluetoothGetLocalAddress()))
364    return device_list
365
366
367def relay_device_discoverability(relay_devices):
368    """To keep relay controlled devices in discoverable mode.
369
370    Args:
371        relay_devices: Relay object.
372
373    Returns:
374        mac_address: Mac address of relay controlled device.
375    """
376    relay_device = relay_devices[0]
377    relay_device.power_on()
378    relay_device.enter_pairing_mode()
379    return relay_device.mac_address
380
381
382def disconnect_headset_from_dev(pri_ad, sec_ad, profiles_list):
383    """Disconnect primary from secondary on a specific set of profiles
384
385    Args:
386        pri_ad: Primary android_device initiating disconnection
387        sec_ad: Secondary android droid (sl4a interface to keep the
388          method signature the same connect_pri_to_sec above)
389        profiles_list: List of profiles we want to disconnect from
390
391    Returns:
392        True on Success
393        False on Failure
394    """
395    supported_profiles = bt_profile_constants.values()
396    for profile in profiles_list:
397        if profile not in supported_profiles:
398            pri_ad.log.info("Profile {} is not in supported list {}".format(
399                profile, supported_profiles))
400            return False
401
402    pri_ad.log.info(pri_ad.droid.bluetoothGetBondedDevices())
403
404    try:
405        pri_ad.droid.bluetoothDisconnectConnectedProfile(sec_ad, profiles_list)
406    except Exception as err:
407        pri_ad.log.error(
408            "Exception while trying to disconnect profile(s) {}: {}".format(
409                profiles_list, err))
410        return False
411
412    profile_disconnected = set()
413    pri_ad.log.info("Disconnecting from profiles: {}".format(profiles_list))
414
415    while not profile_disconnected.issuperset(profiles_list):
416        try:
417            profile_event = pri_ad.ed.pop_event(
418                bluetooth_profile_connection_state_changed, bt_default_timeout)
419            pri_ad.log.info("Got event {}".format(profile_event))
420        except Exception:
421            pri_ad.log.warning("Did not disconnect from Profiles")
422            return True
423
424        profile = profile_event['data']['profile']
425        state = profile_event['data']['state']
426        device_addr = profile_event['data']['addr']
427
428        if state == bt_profile_states['disconnected'] and (
429                device_addr == sec_ad):
430            profile_disconnected.add(profile)
431        pri_ad.log.info(
432            "Profiles disconnected so far {}".format(profile_disconnected))
433
434    return True
435
436
437def initiate_disconnect_from_hf(audio_receiver, pri_ad, sec_ad, duration):
438    """Initiates call and disconnect call on primary device.
439
440    Steps:
441    1. Initiate call from HF.
442    2. Wait for dialing state at DUT and wait for ringing at secondary device.
443    3. Accepts call from secondary device.
444    4. Wait for call active state at primary and secondary device.
445    5. Sleeps until given duration.
446    6. Disconnect call from primary device.
447    7. Wait for call is not present state.
448
449    Args:
450        audio_receiver: An relay device object.
451        pri_ad: An android device to disconnect call.
452        sec_ad: An android device accepting call.
453        duration: Duration of call in seconds.
454
455    Returns:
456        True if successful, False otherwise.
457    """
458    audio_receiver.press_initiate_call()
459    time.sleep(2)
460    flag = True
461    flag &= wait_for_dialing(logging, pri_ad)
462    flag &= wait_for_ringing(logging, sec_ad)
463    if not flag:
464        pri_ad.log.error("Outgoing call did not get established")
465        return False
466
467    if not wait_and_answer_call(logging, sec_ad):
468        pri_ad.log.error("Failed to answer call in second device.")
469        return False
470    if not wait_for_active(logging, pri_ad):
471        pri_ad.log.error("AG not in Active state.")
472        return False
473    if not wait_for_active(logging, sec_ad):
474        pri_ad.log.error("RE not in Active state.")
475        return False
476    time.sleep(duration)
477    if not hangup_call(logging, pri_ad):
478        pri_ad.log.error("Failed to hangup call.")
479        return False
480    flag = True
481    flag &= wait_for_not_in_call(logging, pri_ad)
482    flag &= wait_for_not_in_call(logging, sec_ad)
483    return flag
484
485
486def initiate_disconnect_call_dut(pri_ad, sec_ad, duration, callee_number):
487    """Initiates call and disconnect call on primary device.
488
489    Steps:
490    1. Initiate call from DUT.
491    2. Wait for dialing state at DUT and wait for ringing at secondary device.
492    3. Accepts call from secondary device.
493    4. Wait for call active state at primary and secondary device.
494    5. Sleeps until given duration.
495    6. Disconnect call from primary device.
496    7. Wait for call is not present state.
497
498    Args:
499        pri_ad: An android device to disconnect call.
500        sec_ad: An android device accepting call.
501        duration: Duration of call in seconds.
502        callee_number: Secondary device's phone number.
503
504    Returns:
505        True if successful, False otherwise.
506    """
507    if not initiate_call(logging, pri_ad, callee_number):
508        pri_ad.log.error("Failed to initiate call")
509        return False
510    time.sleep(2)
511
512    flag = True
513    flag &= wait_for_dialing(logging, pri_ad)
514    flag &= wait_for_ringing(logging, sec_ad)
515    if not flag:
516        pri_ad.log.error("Outgoing call did not get established")
517        return False
518
519    if not wait_and_answer_call(logging, sec_ad):
520        pri_ad.log.error("Failed to answer call in second device.")
521        return False
522    # Wait for AG, RE to go into an Active state.
523    if not wait_for_active(logging, pri_ad):
524        pri_ad.log.error("AG not in Active state.")
525        return False
526    if not wait_for_active(logging, sec_ad):
527        pri_ad.log.error("RE not in Active state.")
528        return False
529    time.sleep(duration)
530    if not hangup_call(logging, pri_ad):
531        pri_ad.log.error("Failed to hangup call.")
532        return False
533    flag = True
534    flag &= wait_for_not_in_call(logging, pri_ad)
535    flag &= wait_for_not_in_call(logging, sec_ad)
536
537    return flag
538
539
540def check_wifi_status(pri_ad, network, ssh_config):
541    """Function to check existence of wifi connection.
542
543    Args:
544        pri_ad: An android device.
545        network: network ssid.
546        ssh_config: ssh config for iperf client.
547    """
548    time.sleep(5)
549    proc = subprocess.Popen("pgrep -f 'iperf3 -c'", stdout=subprocess.PIPE, shell=True)
550    pid_list = proc.communicate()[0].decode('utf-8').split()
551
552    while True:
553        p = subprocess.Popen(["pgrep", "-f", "iperf3"], stdout=subprocess.PIPE)
554        process_list = p.communicate()[0].decode('utf-8').split()
555        if not wifi_connection_check(pri_ad, network["SSID"]):
556            pri_ad.adb.shell("killall iperf3")
557            if ssh_config:
558                time.sleep(5)
559                ssh_settings = settings.from_config(ssh_config)
560                ssh_session = connection.SshConnection(ssh_settings)
561                result = ssh_session.run("pgrep iperf3")
562                res = result.stdout.split("\n")
563                for pid in res:
564                    try:
565                        ssh_session.run("kill -9 %s" %pid)
566                    except Exception as e:
567                        logging.warning("No such process: %s" %e)
568                for pid in pid_list[:-1]:
569                    subprocess.Popen("kill -9 {}".format(pid),
570                                 stdout=subprocess.PIPE, shell=True)
571            break
572        elif pid_list[0] not in process_list:
573            break
574
575
576def iperf_result(log, protocol, result):
577    """Accepts the iperf result in json format and parse the output to
578    get throughput value.
579
580    Args:
581        log: Logger object.
582        protocol : TCP or UDP protocol.
583        result: iperf result's filepath.
584
585    Returns:
586        rx_rate: Data received from client.
587    """
588    if os.path.exists(result):
589        ip_cl = IPerfResult(result)
590
591        if protocol == "udp":
592            rx_rate = (math.fsum(ip_cl.instantaneous_rates) /
593                       len(ip_cl.instantaneous_rates))*8
594        else:
595            rx_rate = ip_cl.avg_receive_rate * 8
596        return rx_rate
597    else:
598        log.error("IPerf file not found")
599        return False
600
601
602def is_a2dp_connected(pri_ad, headset_mac_address):
603    """Convenience Function to see if the 2 devices are connected on A2DP.
604
605    Args:
606        pri_ad : An android device.
607        headset_mac_address : Mac address of headset.
608
609    Returns:
610        True:If A2DP connection exists, False otherwise.
611    """
612    devices = pri_ad.droid.bluetoothA2dpGetConnectedDevices()
613    for device in devices:
614        pri_ad.log.debug("A2dp Connected device {}".format(device["name"]))
615        if device["address"] == headset_mac_address:
616            return True
617    return False
618
619
620def media_stream_check(pri_ad, duration, headset_mac_address):
621    """Checks whether A2DP connecion is active or not for given duration of
622    time.
623
624    Args:
625        pri_ad : An android device.
626        duration : No of seconds to check if a2dp streaming is alive.
627        headset_mac_address : Headset mac address.
628
629    Returns:
630        True: If A2dp connection is active for entire duration.
631        False: If A2dp connection is not active.
632    """
633    while time.time() < duration:
634        if not is_a2dp_connected(pri_ad, headset_mac_address):
635            pri_ad.log.error('A2dp connection not active at %s', pri_ad.serial)
636            return False
637        time.sleep(1)
638    return True
639
640
641def multithread_func(log, tasks):
642    """Multi-thread function wrapper.
643
644    Args:
645        log: log object.
646        tasks: tasks to be executed in parallel.
647
648    Returns:
649       List of results of tasks
650    """
651    results = run_multithread_func(log, tasks)
652    for res in results:
653        if not res:
654            return False
655    return True
656
657
658def music_play_and_check(pri_ad, headset_mac_address, music_to_play, duration):
659    """Starts playing media and checks if media plays for n seconds.
660
661    Steps:
662    1. Starts media player on android device.
663    2. Checks if music streaming is ongoing for n seconds.
664    3. Stops media player.
665    4. Collect dumpsys logs.
666
667    Args:
668        pri_ad: An android device.
669        headset_mac_address: Mac address of third party headset.
670        music_to_play: Indicates the music file to play.
671        duration: Time in secs to indicate music time to play.
672
673    Returns:
674        True if successful, False otherwise.
675    """
676    pri_ad.droid.setMediaVolume(pri_ad.droid.getMaxMediaVolume() - 1)
677    pri_ad.log.info("current volume = {}".format(pri_ad.droid.getMediaVolume()))
678    pri_ad.log.debug("In music play and check")
679    if not start_media_play(pri_ad, music_to_play):
680        pri_ad.log.error("Start media play failed.")
681        return False
682    stream_time = time.time() + duration
683    if not media_stream_check(pri_ad, stream_time, headset_mac_address):
684        pri_ad.log.error("A2DP Connection check failed.")
685        pri_ad.droid.mediaPlayStop()
686        return False
687    pri_ad.droid.mediaPlayStop()
688    return True
689
690
691def music_play_and_check_via_app(pri_ad, headset_mac_address, duration=5):
692    """Starts google music player and check for A2DP connection.
693
694    Steps:
695    1. Starts Google music player on android device.
696    2. Checks for A2DP connection.
697
698    Args:
699        pri_ad: An android device.
700        headset_mac_address: Mac address of third party headset.
701        duration: Total time of music streaming.
702
703    Returns:
704        True if successful, False otherwise.
705    """
706    pri_ad.adb.shell("am start com.google.android.music")
707    time.sleep(3)
708    pri_ad.adb.shell("input keyevent 85")
709    stream_time = time.time() + duration
710    try:
711        if not media_stream_check(pri_ad, stream_time, headset_mac_address):
712            pri_ad.log.error("A2dp connection not active at %s", pri_ad.serial)
713            return False
714    finally:
715        pri_ad.adb.shell("am force-stop com.google.android.music")
716        return True
717
718
719def get_phone_ip(ad):
720    """Get the WiFi IP address of the phone.
721
722    Args:
723        ad: the android device under test
724
725    Returns:
726        Ip address of the phone for WiFi, as a string
727    """
728    return ad.droid.connectivityGetIPv4Addresses('wlan0')[0]
729
730
731def pair_dev_to_headset(pri_ad, dev_to_pair):
732    """Pairs pri droid to secondary droid.
733
734    Args:
735        pri_ad: Android device initiating connection
736        dev_to_pair: Third party headset mac address.
737
738    Returns:
739        True if Pass
740        False if Fail
741    """
742    bonded_devices = pri_ad.droid.bluetoothGetBondedDevices()
743    for d in bonded_devices:
744        if d['address'] == dev_to_pair:
745            pri_ad.log.info("Successfully bonded to device".format(dev_to_pair))
746            return True
747    pri_ad.droid.bluetoothStartDiscovery()
748    time.sleep(10)  #Wait until device gets discovered
749    pri_ad.droid.bluetoothCancelDiscovery()
750    pri_ad.log.debug("discovered devices = {}".format(
751        pri_ad.droid.bluetoothGetDiscoveredDevices()))
752    for device in pri_ad.droid.bluetoothGetDiscoveredDevices():
753        if device['address'] == dev_to_pair:
754
755            result = pri_ad.droid.bluetoothDiscoverAndBond(dev_to_pair)
756            pri_ad.log.info(result)
757            end_time = time.time() + bt_default_timeout
758            pri_ad.log.info("Verifying devices are bonded")
759            time.sleep(5)  #Wait time until device gets paired.
760            while time.time() < end_time:
761                bonded_devices = pri_ad.droid.bluetoothGetBondedDevices()
762                bonded = False
763                for d in bonded_devices:
764                    if d['address'] == dev_to_pair:
765                        pri_ad.log.info(
766                            "Successfully bonded to device".format(dev_to_pair))
767                        return True
768    pri_ad.log.info("Failed to bond devices.")
769    return False
770
771
772def pair_and_connect_headset(pri_ad, headset_mac_address, profile_to_connect, retry=5):
773    """Pair and connect android device with third party headset.
774
775    Args:
776        pri_ad: An android device.
777        headset_mac_address: Mac address of third party headset.
778        profile_to_connect: Profile to be connected with headset.
779        retry: Number of times pair and connection should happen.
780
781    Returns:
782        True if pair and connect to headset successful, False otherwise.
783    """
784
785    paired = False
786    for _ in range(retry):
787        if pair_dev_to_headset(pri_ad, headset_mac_address):
788            paired = True
789            break
790        else:
791            pri_ad.log.error("Could not pair to headset. Retrying.")
792
793    time.sleep(2)  # Wait until pairing gets over.
794
795    if paired:
796        for _ in range(retry):
797            if connect_dev_to_headset(pri_ad, headset_mac_address,
798                                      profile_to_connect):
799                return True
800            else:
801                pri_ad.log.error("Could not connect to headset. Retrying.")
802    else:
803        asserts.fail("Failed to pair and connect to headset")
804
805
806def perform_classic_discovery(pri_ad, duration, file_name, dev_list=None):
807    """Convenience function to start and stop device discovery.
808
809    Args:
810        pri_ad: An android device.
811        duration: iperf duration of the test.
812        file_name: Json file to which result is dumped
813        dev_list: List of devices to be discoverable mode.
814
815    Returns:
816        True start and stop discovery is successful, False otherwise.
817    """
818    if dev_list:
819        devices_required = device_discoverability(dev_list)
820    else:
821        devices_required = None
822    iteration = 0
823    result = {}
824    result["discovered_devices"] = {}
825    discover_result = []
826    start_time = time.time()
827    while time.time() < start_time + duration:
828        if not pri_ad.droid.bluetoothStartDiscovery():
829            pri_ad.log.error("Failed to start discovery")
830            return False
831        time.sleep(DISCOVERY_TIME)
832        if not pri_ad.droid.bluetoothCancelDiscovery():
833            pri_ad.log.error("Failed to cancel discovery")
834            return False
835        pri_ad.log.info("Discovered device list {}".format(
836            pri_ad.droid.bluetoothGetDiscoveredDevices()))
837        if devices_required is not None:
838            result["discovered_devices"][iteration] = []
839            devices_name = {
840                element.get('name', element['address'])
841                for element in pri_ad.droid.bluetoothGetDiscoveredDevices()
842                if element["address"] in devices_required
843            }
844            result["discovered_devices"][iteration] = list(devices_name)
845            discover_result.extend([len(devices_name) == len(devices_required)])
846            iteration += 1
847            with open(file_name, 'a') as results_file:
848                json.dump(result, results_file, indent=4)
849            if False in discover_result:
850                return False
851        else:
852            pri_ad.log.warning("No devices are kept in discoverable mode")
853    return True
854
855
856def connect_wlan_profile(pri_ad, network):
857    """Disconnect and Connect to AP.
858
859    Args:
860        pri_ad: An android device.
861        network: Network to which AP to be connected.
862
863    Returns:
864        True if successful, False otherwise.
865    """
866    reset_wifi(pri_ad)
867    wifi_toggle_state(pri_ad, False)
868    wifi_test_device_init(pri_ad)
869    wifi_connect(pri_ad, network)
870    if not wifi_connection_check(pri_ad, network["SSID"]):
871        pri_ad.log.error("Wifi connection does not exist.")
872        return False
873    return True
874
875
876def toggle_bluetooth(pri_ad, duration):
877    """Toggles bluetooth on/off for N iterations.
878
879    Args:
880        pri_ad: An android device object.
881        duration: Iperf duration of the test.
882
883    Returns:
884        True if successful, False otherwise.
885    """
886    start_time = time.time()
887    while time.time() < start_time + duration:
888        if not enable_bluetooth(pri_ad.droid, pri_ad.ed):
889            pri_ad.log.error("Failed to enable bluetooth")
890            return False
891        time.sleep(BLUETOOTH_WAIT_TIME)
892        if not disable_bluetooth(pri_ad.droid):
893            pri_ad.log.error("Failed to turn off bluetooth")
894            return False
895        time.sleep(BLUETOOTH_WAIT_TIME)
896    return True
897
898
899def toggle_screen_state(pri_ad, duration):
900    """Toggles the screen state to on or off..
901
902    Args:
903        pri_ad: Android device.
904        duration: Iperf duration of the test.
905
906    Returns:
907        True if successful, False otherwise.
908    """
909    start_time = time.time()
910    while time.time() < start_time + duration:
911        if not pri_ad.ensure_screen_on():
912            pri_ad.log.error("User window cannot come up")
913            return False
914        if not pri_ad.go_to_sleep():
915            pri_ad.log.info("Screen off")
916    return True
917
918
919def setup_tel_config(pri_ad, sec_ad, sim_conf_file):
920    """Sets tel properties for primary device and secondary devices
921
922    Args:
923        pri_ad: An android device object.
924        sec_ad: An android device object.
925        sim_conf_file: Sim card map.
926
927    Returns:
928        pri_ad_num: Phone number of primary device.
929        sec_ad_num: Phone number of secondary device.
930    """
931    setup_droid_properties(logging, pri_ad, sim_conf_file)
932    pri_ad_num = get_phone_number(logging, pri_ad)
933    setup_droid_properties(logging, sec_ad, sim_conf_file)
934    sec_ad_num = get_phone_number(logging, sec_ad)
935    return pri_ad_num, sec_ad_num
936
937
938def start_fping(pri_ad, duration, fping_params):
939    """Starts fping to ping for DUT's ip address.
940
941    Steps:
942    1. Run fping command to check DUT's IP is alive or not.
943
944    Args:
945        pri_ad: An android device object.
946        duration: Duration of fping in seconds.
947        fping_params: List of parameters for fping to run.
948
949    Returns:
950        True if successful, False otherwise.
951    """
952    counter = 0
953    fping_path = ''.join((pri_ad.log_path, "/Fping"))
954    create_dir(fping_path)
955    while os.path.isfile(fping_path + "/fping_%s.txt" % counter):
956        counter += 1
957    out_file_name = "{}".format("fping_%s.txt" % counter)
958
959    full_out_path = os.path.join(fping_path, out_file_name)
960    cmd = "fping {} -D -c {}".format(get_phone_ip(pri_ad), duration)
961    if fping_params["ssh_config"]:
962        ssh_settings = settings.from_config(fping_params["ssh_config"])
963        ssh_session = connection.SshConnection(ssh_settings)
964        try:
965            with open(full_out_path, 'w') as outfile:
966                job_result = ssh_session.run(cmd)
967                outfile.write(job_result.stdout)
968                outfile.write("\n")
969                outfile.writelines(job_result.stderr)
970        except Exception as err:
971            pri_ad.log.error("Fping run has been failed. = {}".format(err))
972            return False
973    else:
974        cmd = cmd.split()
975        with open(full_out_path, "w") as f:
976            subprocess.call(cmd, stderr=f, stdout=f)
977    result = parse_fping_results(fping_params["fping_drop_tolerance"],
978                                 full_out_path)
979    return bool(result)
980
981
982def parse_fping_results(failure_rate, full_out_path):
983    """Calculates fping results.
984
985    Steps:
986    1. Read the file and calculate the results.
987
988    Args:
989        failure_rate: Fping packet drop tolerance value.
990        full_out_path: path where the fping results has been stored.
991
992    Returns:
993        loss_percent: loss percentage of fping packet.
994    """
995    try:
996        result_file = open(full_out_path, "r")
997        lines = result_file.readlines()
998        res_line = lines[-1]
999        # Ex: res_line = "192.168.186.224 : xmt/rcv/%loss = 10/10/0%,
1000        # min/avg/max = 36.7/251/1272"
1001        loss_percent = re.search("[0-9]+%", res_line)
1002        if int(loss_percent.group().strip("%")) > failure_rate:
1003            logging.error("Packet drop observed")
1004            return False
1005        return loss_percent.group()
1006    except Exception as e:
1007        logging.error("Error in parsing fping results : %s" %(e))
1008        return False
1009
1010
1011def start_media_play(pri_ad, music_file_to_play):
1012    """Starts media player on device.
1013
1014    Args:
1015        pri_ad : An android device.
1016        music_file_to_play : An audio file to play.
1017
1018    Returns:
1019        True:If media player start music, False otherwise.
1020    """
1021    if not pri_ad.droid.mediaPlayOpen(
1022            "file:///sdcard/Music/{}".format(music_file_to_play)):
1023        pri_ad.log.error("Failed to play music")
1024        return False
1025
1026    pri_ad.droid.mediaPlaySetLooping(True)
1027    pri_ad.log.info("Music is now playing on device {}".format(pri_ad.serial))
1028    return True
1029
1030
1031def wifi_connection_check(pri_ad, ssid):
1032    """Function to check existence of wifi connection.
1033
1034    Args:
1035        pri_ad : An android device.
1036        ssid : wifi ssid to check.
1037
1038    Returns:
1039        True if wifi connection exists, False otherwise.
1040    """
1041    wifi_info = pri_ad.droid.wifiGetConnectionInfo()
1042    if (wifi_info["SSID"] == ssid and
1043            wifi_info["supplicant_state"] == "completed"):
1044        return True
1045    pri_ad.log.error("Wifi Connection check failed : {}".format(wifi_info))
1046    return False
1047
1048
1049def push_music_to_android_device(ad, audio_params):
1050    """Add music to Android device as specified by the test config
1051
1052    Args:
1053        ad: Android device
1054        audio_params: Music file to push.
1055
1056    Returns:
1057        True on success, False on failure
1058    """
1059    ad.log.info("Pushing music to the Android device")
1060    android_music_path = "/sdcard/Music/"
1061    music_path = audio_params["music_file"]
1062    if type(music_path) is list:
1063        ad.log.info("Media ready to push as is.")
1064        for item in music_path:
1065            music_file_to_play = item
1066            ad.adb.push(item, android_music_path)
1067        return music_file_to_play
1068    else:
1069        music_file_to_play = audio_params["music_file"]
1070        ad.adb.push("{} {}".format(music_file_to_play, android_music_path))
1071        return (os.path.basename(music_file_to_play))
1072
1073
1074def bokeh_chart_plot(bt_attenuation_range,
1075               data_sets,
1076               legends,
1077               fig_property,
1078               shaded_region=None,
1079               output_file_path=None):
1080    """Plot bokeh figs.
1081
1082    Args:
1083        bt_attenuation_range: range of BT attenuation.
1084        data_sets: data sets including lists of x_data and lists of y_data
1085            ex: [[[x_data1], [x_data2]], [[y_data1],[y_data2]]]
1086        legends: list of legend for each curve
1087        fig_property: dict containing the plot property, including title,
1088                      lables, linewidth, circle size, etc.
1089        shaded_region: optional dict containing data for plot shading
1090        output_file_path: optional path at which to save figure
1091
1092    Returns:
1093        plot: bokeh plot figure object
1094    """
1095    TOOLS = ('box_zoom,box_select,pan,crosshair,redo,undo,reset,hover,save')
1096    colors = [
1097        'red', 'green', 'blue', 'olive', 'orange', 'salmon', 'black', 'navy',
1098        'yellow', 'darkred', 'goldenrod'
1099    ]
1100    plot = []
1101    data = [[], []]
1102    legend = []
1103    for i in bt_attenuation_range:
1104        if "Packet drop" in legends[i][0]:
1105            plot_info = {0: "A2dp_packet_drop_plot", 1: "throughput_plot"}
1106        else:
1107            plot_info = {0: "throughput_plot"}
1108        for j in plot_info:
1109            if "Packet drops" in legends[i][j]:
1110                if data_sets[i]["a2dp_packet_drops"]:
1111                    plot_i_j = figure(
1112                        plot_width=1000,
1113                        plot_height=500,
1114                        title=fig_property['title'],
1115                        tools=TOOLS)
1116
1117                    plot_i_j.add_tools(
1118                        bokeh_tools.WheelZoomTool(dimensions="width"))
1119                    plot_i_j.add_tools(
1120                        bokeh_tools.WheelZoomTool(dimensions="height"))
1121                    plot_i_j.xaxis.axis_label = fig_property['x_label']
1122                    plot_i_j.yaxis.axis_label = fig_property['y_label'][j]
1123                    plot_i_j.legend.location = "top_right"
1124                    plot_i_j.legend.click_policy = "hide"
1125                    plot_i_j.title.text_font_size = {'value': '15pt'}
1126
1127                    plot_i_j.line(
1128                        data_sets[i]["a2dp_attenuation"],
1129                        data_sets[i]["a2dp_packet_drops"],
1130                        legend=legends[i][j],
1131                        line_width=3,
1132                        color=colors[j])
1133                    plot_i_j.circle(
1134                        data_sets[i]["a2dp_attenuation"],
1135                        data_sets[i]["a2dp_packet_drops"],
1136                        legend=str(legends[i][j]),
1137                        fill_color=colors[j])
1138                    plot.append(plot_i_j)
1139            elif "Performance Results" in legends[i][j]:
1140                plot_i_j = figure(
1141                    plot_width=1000,
1142                    plot_height=500,
1143                    title=fig_property['title'],
1144                    tools=TOOLS)
1145                plot_i_j.add_tools(
1146                    bokeh_tools.WheelZoomTool(dimensions="width"))
1147                plot_i_j.add_tools(
1148                    bokeh_tools.WheelZoomTool(dimensions="height"))
1149                plot_i_j.xaxis.axis_label = fig_property['x_label']
1150                plot_i_j.yaxis.axis_label = fig_property['y_label'][j]
1151                plot_i_j.legend.location = "top_right"
1152                plot_i_j.legend.click_policy = "hide"
1153                plot_i_j.title.text_font_size = {'value': '15pt'}
1154                data[0].insert(0, data_sets[i]["attenuation"])
1155                data[1].insert(0, data_sets[i]["throughput_received"])
1156                legend.insert(0, legends[i][j + 1])
1157                plot_i_j.line(
1158                    data_sets[i]["user_attenuation"],
1159                    data_sets[i]["user_throughput"],
1160                    legend=legends[i][j],
1161                    line_width=3,
1162                    color=colors[j])
1163                plot_i_j.circle(
1164                    data_sets[i]["user_attenuation"],
1165                    data_sets[i]["user_throughput"],
1166                    legend=str(legends[i][j]),
1167                    fill_color=colors[j])
1168                plot_i_j.line(
1169                    data_sets[i]["attenuation"],
1170                    data_sets[i]["throughput_received"],
1171                    legend=legends[i][j + 1],
1172                    line_width=3,
1173                    color=colors[j])
1174                plot_i_j.circle(
1175                    data_sets[i]["attenuation"],
1176                    data_sets[i]["throughput_received"],
1177                    legend=str(legends[i][j + 1]),
1178                    fill_color=colors[j])
1179                if shaded_region:
1180                    band_x = shaded_region[i]["x_vector"]
1181                    band_x.extend(shaded_region[i]["x_vector"][::-1])
1182                    band_y = shaded_region[i]["lower_limit"]
1183                    band_y.extend(shaded_region[i]["upper_limit"][::-1])
1184                    plot_i_j.patch(
1185                        band_x,
1186                        band_y,
1187                        color='#7570B3',
1188                        line_alpha=0.1,
1189                        fill_alpha=0.1)
1190                plot.append(plot_i_j)
1191            else:
1192                plot_i_j = figure(
1193                    plot_width=1000,
1194                    plot_height=500,
1195                    title=fig_property['title'],
1196                    tools=TOOLS)
1197                plot_i_j.add_tools(
1198                    bokeh_tools.WheelZoomTool(dimensions="width"))
1199                plot_i_j.add_tools(
1200                    bokeh_tools.WheelZoomTool(dimensions="height"))
1201                plot_i_j.xaxis.axis_label = fig_property['x_label']
1202                plot_i_j.yaxis.axis_label = fig_property['y_label'][j]
1203                plot_i_j.legend.location = "top_right"
1204                plot_i_j.legend.click_policy = "hide"
1205                plot_i_j.title.text_font_size = {'value': '15pt'}
1206                data[0].insert(0, data_sets[i]["attenuation"])
1207                data[1].insert(0, data_sets[i]["throughput_received"])
1208                legend.insert(0, legends[i][j])
1209                plot_i_j.line(
1210                    data_sets[i]["attenuation"],
1211                    data_sets[i]["throughput_received"],
1212                    legend=legends[i][j],
1213                    line_width=3,
1214                    color=colors[j])
1215                plot_i_j.circle(
1216                    data_sets[i]["attenuation"],
1217                    data_sets[i]["throughput_received"],
1218                    legend=str(legends[i][j]),
1219                    fill_color=colors[j])
1220                plot.append(plot_i_j)
1221    fig_property['y_label'] = "Throughput (Mbps)"
1222    all_plot = bokeh_plot(data, legend, fig_property, shaded_region=None,
1223            output_file_path=None)
1224    plot.insert(0, all_plot)
1225    if output_file_path is not None:
1226        output_file(output_file_path)
1227        save(column(plot))
1228    return plot
1229
1230
1231class A2dpDumpsysParser():
1232
1233    def __init__(self):
1234        self.count_list = []
1235        self.frame_list = []
1236        self.dropped_count = None
1237
1238    def parse(self, file_path):
1239        """Convenience function to parse a2dp dumpsys logs.
1240
1241        Args:
1242            file_path: Path of dumpsys logs.
1243
1244        Returns:
1245            dropped_list containing packet drop count for every iteration.
1246            drop containing list of all packets dropped for test suite.
1247        """
1248        a2dp_dumpsys_info = []
1249        with open(file_path) as dumpsys_file:
1250            for line in dumpsys_file:
1251                if "A2DP State:" in line:
1252                    a2dp_dumpsys_info.append(line)
1253                elif "Counts (max dropped)" not in line and len(
1254                        a2dp_dumpsys_info) > 0:
1255                    a2dp_dumpsys_info.append(line)
1256                elif "Counts (max dropped)" in line:
1257                    a2dp_dumpsys_info = ''.join(a2dp_dumpsys_info)
1258                    a2dp_info = a2dp_dumpsys_info.split("\n")
1259                    # Ex: Frames per packet (total/max/ave) : 5034 / 1 / 0
1260                    frames = int(re.split("[':/()]", str(a2dp_info[-3]))[-3])
1261                    self.frame_list.append(frames)
1262                    # Ex : Counts (flushed/dropped/dropouts) : 0 / 4 / 0
1263                    count = int(re.split("[':/()]", str(a2dp_info[-2]))[-2])
1264                    if count > 0:
1265                        for i in range(len(self.count_list)):
1266                            count = count - self.count_list[i]
1267                        self.count_list.append(count)
1268                        if len(self.frame_list) > 1:
1269                            last_frame = self.frame_list[-1] - self.frame_list[
1270                                -2]
1271                            self.dropped_count = (count / last_frame) * 100
1272                        else:
1273                            self.dropped_count = (
1274                                count / self.frame_list[-1]) * 100
1275                    else:
1276                        self.dropped_count = count
1277                    logging.info(a2dp_dumpsys_info)
1278                    return self.dropped_count
1279