1#!/usr/bin/env python3
2#
3#   Copyright 2016 Google, Inc.
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 logging
18import os
19import re
20import shutil
21import time
22
23from collections import namedtuple
24from enum import IntEnum
25from queue import Empty
26
27from acts import asserts
28from acts import context
29from acts import signals
30from acts import utils
31from acts.controllers import attenuator
32from acts.controllers.ap_lib import hostapd_security
33from acts.controllers.ap_lib import hostapd_ap_preset
34from acts.controllers.ap_lib.hostapd_constants import BAND_2G
35from acts.controllers.ap_lib.hostapd_constants import BAND_5G
36from acts.test_utils.wifi import wifi_constants
37from acts.test_utils.tel import tel_defines
38
39# Default timeout used for reboot, toggle WiFi and Airplane mode,
40# for the system to settle down after the operation.
41DEFAULT_TIMEOUT = 10
42# Number of seconds to wait for events that are supposed to happen quickly.
43# Like onSuccess for start background scan and confirmation on wifi state
44# change.
45SHORT_TIMEOUT = 30
46ROAMING_TIMEOUT = 30
47WIFI_CONNECTION_TIMEOUT_DEFAULT = 30
48# Speed of light in m/s.
49SPEED_OF_LIGHT = 299792458
50
51DEFAULT_PING_ADDR = "https://www.google.com/robots.txt"
52
53ROAMING_ATTN = {
54        "AP1_on_AP2_off": [
55            0,
56            0,
57            95,
58            95
59        ],
60        "AP1_off_AP2_on": [
61            95,
62            95,
63            0,
64            0
65        ],
66        "default": [
67            0,
68            0,
69            0,
70            0
71        ]
72    }
73
74
75class WifiEnums():
76
77    SSID_KEY = "SSID" # Used for Wifi & SoftAp
78    SSID_PATTERN_KEY = "ssidPattern"
79    NETID_KEY = "network_id"
80    BSSID_KEY = "BSSID" # Used for Wifi & SoftAp
81    BSSID_PATTERN_KEY = "bssidPattern"
82    PWD_KEY = "password" # Used for Wifi & SoftAp
83    frequency_key = "frequency"
84    HIDDEN_KEY = "hiddenSSID" # Used for Wifi & SoftAp
85    IS_APP_INTERACTION_REQUIRED = "isAppInteractionRequired"
86    IS_USER_INTERACTION_REQUIRED = "isUserInteractionRequired"
87    IS_SUGGESTION_METERED = "isMetered"
88    PRIORITY = "priority"
89    SECURITY = "security" # Used for Wifi & SoftAp
90
91    # Used for SoftAp
92    AP_BAND_KEY = "apBand"
93    AP_CHANNEL_KEY = "apChannel"
94    AP_MAXCLIENTS_KEY = "MaxNumberOfClients"
95    AP_SHUTDOWNTIMEOUT_KEY = "ShutdownTimeoutMillis"
96    AP_SHUTDOWNTIMEOUTENABLE_KEY = "AutoShutdownEnabled"
97    AP_CLIENTCONTROL_KEY = "ClientControlByUserEnabled"
98    AP_ALLOWEDLIST_KEY = "AllowedClientList"
99    AP_BLOCKEDLIST_KEY = "BlockedClientList"
100
101    WIFI_CONFIG_SOFTAP_BAND_2G = 1
102    WIFI_CONFIG_SOFTAP_BAND_5G = 2
103    WIFI_CONFIG_SOFTAP_BAND_2G_5G = 3
104    WIFI_CONFIG_SOFTAP_BAND_6G = 4
105    WIFI_CONFIG_SOFTAP_BAND_2G_6G = 5
106    WIFI_CONFIG_SOFTAP_BAND_5G_6G = 6
107    WIFI_CONFIG_SOFTAP_BAND_ANY = 7
108
109    # DO NOT USE IT for new test case! Replaced by WIFI_CONFIG_SOFTAP_BAND_
110    WIFI_CONFIG_APBAND_2G = WIFI_CONFIG_SOFTAP_BAND_2G
111    WIFI_CONFIG_APBAND_5G = WIFI_CONFIG_SOFTAP_BAND_5G
112    WIFI_CONFIG_APBAND_AUTO = WIFI_CONFIG_SOFTAP_BAND_2G_5G
113
114    WIFI_CONFIG_APBAND_2G_OLD = 0
115    WIFI_CONFIG_APBAND_5G_OLD = 1
116    WIFI_CONFIG_APBAND_AUTO_OLD = -1
117
118    WIFI_WPS_INFO_PBC = 0
119    WIFI_WPS_INFO_DISPLAY = 1
120    WIFI_WPS_INFO_KEYPAD = 2
121    WIFI_WPS_INFO_LABEL = 3
122    WIFI_WPS_INFO_INVALID = 4
123
124    class SoftApSecurityType():
125        OPEN = "NONE"
126        WPA2 = "WPA2_PSK"
127        WPA3_SAE_TRANSITION = "WPA3_SAE_TRANSITION"
128        WPA3_SAE = "WPA3_SAE"
129
130    class CountryCode():
131        CHINA = "CN"
132        JAPAN = "JP"
133        UK = "GB"
134        US = "US"
135        UNKNOWN = "UNKNOWN"
136
137    # Start of Macros for EAP
138    # EAP types
139    class Eap(IntEnum):
140        NONE = -1
141        PEAP = 0
142        TLS = 1
143        TTLS = 2
144        PWD = 3
145        SIM = 4
146        AKA = 5
147        AKA_PRIME = 6
148        UNAUTH_TLS = 7
149
150    # EAP Phase2 types
151    class EapPhase2(IntEnum):
152        NONE = 0
153        PAP = 1
154        MSCHAP = 2
155        MSCHAPV2 = 3
156        GTC = 4
157
158    class Enterprise:
159        # Enterprise Config Macros
160        EMPTY_VALUE = "NULL"
161        EAP = "eap"
162        PHASE2 = "phase2"
163        IDENTITY = "identity"
164        ANON_IDENTITY = "anonymous_identity"
165        PASSWORD = "password"
166        SUBJECT_MATCH = "subject_match"
167        ALTSUBJECT_MATCH = "altsubject_match"
168        DOM_SUFFIX_MATCH = "domain_suffix_match"
169        CLIENT_CERT = "client_cert"
170        CA_CERT = "ca_cert"
171        ENGINE = "engine"
172        ENGINE_ID = "engine_id"
173        PRIVATE_KEY_ID = "key_id"
174        REALM = "realm"
175        PLMN = "plmn"
176        FQDN = "FQDN"
177        FRIENDLY_NAME = "providerFriendlyName"
178        ROAMING_IDS = "roamingConsortiumIds"
179        OCSP = "ocsp"
180    # End of Macros for EAP
181
182    # Macros for wifi p2p.
183    WIFI_P2P_SERVICE_TYPE_ALL = 0
184    WIFI_P2P_SERVICE_TYPE_BONJOUR = 1
185    WIFI_P2P_SERVICE_TYPE_UPNP = 2
186    WIFI_P2P_SERVICE_TYPE_VENDOR_SPECIFIC = 255
187
188    class ScanResult:
189        CHANNEL_WIDTH_20MHZ = 0
190        CHANNEL_WIDTH_40MHZ = 1
191        CHANNEL_WIDTH_80MHZ = 2
192        CHANNEL_WIDTH_160MHZ = 3
193        CHANNEL_WIDTH_80MHZ_PLUS_MHZ = 4
194
195    # Macros for wifi rtt.
196    class RttType(IntEnum):
197        TYPE_ONE_SIDED = 1
198        TYPE_TWO_SIDED = 2
199
200    class RttPeerType(IntEnum):
201        PEER_TYPE_AP = 1
202        PEER_TYPE_STA = 2  # Requires NAN.
203        PEER_P2P_GO = 3
204        PEER_P2P_CLIENT = 4
205        PEER_NAN = 5
206
207    class RttPreamble(IntEnum):
208        PREAMBLE_LEGACY = 0x01
209        PREAMBLE_HT = 0x02
210        PREAMBLE_VHT = 0x04
211
212    class RttBW(IntEnum):
213        BW_5_SUPPORT = 0x01
214        BW_10_SUPPORT = 0x02
215        BW_20_SUPPORT = 0x04
216        BW_40_SUPPORT = 0x08
217        BW_80_SUPPORT = 0x10
218        BW_160_SUPPORT = 0x20
219
220    class Rtt(IntEnum):
221        STATUS_SUCCESS = 0
222        STATUS_FAILURE = 1
223        STATUS_FAIL_NO_RSP = 2
224        STATUS_FAIL_REJECTED = 3
225        STATUS_FAIL_NOT_SCHEDULED_YET = 4
226        STATUS_FAIL_TM_TIMEOUT = 5
227        STATUS_FAIL_AP_ON_DIFF_CHANNEL = 6
228        STATUS_FAIL_NO_CAPABILITY = 7
229        STATUS_ABORTED = 8
230        STATUS_FAIL_INVALID_TS = 9
231        STATUS_FAIL_PROTOCOL = 10
232        STATUS_FAIL_SCHEDULE = 11
233        STATUS_FAIL_BUSY_TRY_LATER = 12
234        STATUS_INVALID_REQ = 13
235        STATUS_NO_WIFI = 14
236        STATUS_FAIL_FTM_PARAM_OVERRIDE = 15
237
238        REASON_UNSPECIFIED = -1
239        REASON_NOT_AVAILABLE = -2
240        REASON_INVALID_LISTENER = -3
241        REASON_INVALID_REQUEST = -4
242
243    class RttParam:
244        device_type = "deviceType"
245        request_type = "requestType"
246        BSSID = "bssid"
247        channel_width = "channelWidth"
248        frequency = "frequency"
249        center_freq0 = "centerFreq0"
250        center_freq1 = "centerFreq1"
251        number_burst = "numberBurst"
252        interval = "interval"
253        num_samples_per_burst = "numSamplesPerBurst"
254        num_retries_per_measurement_frame = "numRetriesPerMeasurementFrame"
255        num_retries_per_FTMR = "numRetriesPerFTMR"
256        lci_request = "LCIRequest"
257        lcr_request = "LCRRequest"
258        burst_timeout = "burstTimeout"
259        preamble = "preamble"
260        bandwidth = "bandwidth"
261        margin = "margin"
262
263    RTT_MARGIN_OF_ERROR = {
264        RttBW.BW_80_SUPPORT: 2,
265        RttBW.BW_40_SUPPORT: 5,
266        RttBW.BW_20_SUPPORT: 5
267    }
268
269    # Macros as specified in the WifiScanner code.
270    WIFI_BAND_UNSPECIFIED = 0  # not specified
271    WIFI_BAND_24_GHZ = 1  # 2.4 GHz band
272    WIFI_BAND_5_GHZ = 2  # 5 GHz band without DFS channels
273    WIFI_BAND_5_GHZ_DFS_ONLY = 4  # 5 GHz band with DFS channels
274    WIFI_BAND_5_GHZ_WITH_DFS = 6  # 5 GHz band with DFS channels
275    WIFI_BAND_BOTH = 3  # both bands without DFS channels
276    WIFI_BAND_BOTH_WITH_DFS = 7  # both bands with DFS channels
277
278    REPORT_EVENT_AFTER_BUFFER_FULL = 0
279    REPORT_EVENT_AFTER_EACH_SCAN = 1
280    REPORT_EVENT_FULL_SCAN_RESULT = 2
281
282    SCAN_TYPE_LOW_LATENCY = 0
283    SCAN_TYPE_LOW_POWER = 1
284    SCAN_TYPE_HIGH_ACCURACY = 2
285
286    # US Wifi frequencies
287    ALL_2G_FREQUENCIES = [2412, 2417, 2422, 2427, 2432, 2437, 2442, 2447, 2452,
288                          2457, 2462]
289    DFS_5G_FREQUENCIES = [5260, 5280, 5300, 5320, 5500, 5520, 5540, 5560, 5580,
290                          5600, 5620, 5640, 5660, 5680, 5700, 5720]
291    NONE_DFS_5G_FREQUENCIES = [5180, 5200, 5220, 5240, 5745, 5765, 5785, 5805,
292                               5825]
293    ALL_5G_FREQUENCIES = DFS_5G_FREQUENCIES + NONE_DFS_5G_FREQUENCIES
294
295    band_to_frequencies = {
296        WIFI_BAND_24_GHZ: ALL_2G_FREQUENCIES,
297        WIFI_BAND_5_GHZ: NONE_DFS_5G_FREQUENCIES,
298        WIFI_BAND_5_GHZ_DFS_ONLY: DFS_5G_FREQUENCIES,
299        WIFI_BAND_5_GHZ_WITH_DFS: ALL_5G_FREQUENCIES,
300        WIFI_BAND_BOTH: ALL_2G_FREQUENCIES + NONE_DFS_5G_FREQUENCIES,
301        WIFI_BAND_BOTH_WITH_DFS: ALL_5G_FREQUENCIES + ALL_2G_FREQUENCIES
302    }
303
304    # All Wifi frequencies to channels lookup.
305    freq_to_channel = {
306        2412: 1,
307        2417: 2,
308        2422: 3,
309        2427: 4,
310        2432: 5,
311        2437: 6,
312        2442: 7,
313        2447: 8,
314        2452: 9,
315        2457: 10,
316        2462: 11,
317        2467: 12,
318        2472: 13,
319        2484: 14,
320        4915: 183,
321        4920: 184,
322        4925: 185,
323        4935: 187,
324        4940: 188,
325        4945: 189,
326        4960: 192,
327        4980: 196,
328        5035: 7,
329        5040: 8,
330        5045: 9,
331        5055: 11,
332        5060: 12,
333        5080: 16,
334        5170: 34,
335        5180: 36,
336        5190: 38,
337        5200: 40,
338        5210: 42,
339        5220: 44,
340        5230: 46,
341        5240: 48,
342        5260: 52,
343        5280: 56,
344        5300: 60,
345        5320: 64,
346        5500: 100,
347        5520: 104,
348        5540: 108,
349        5560: 112,
350        5580: 116,
351        5600: 120,
352        5620: 124,
353        5640: 128,
354        5660: 132,
355        5680: 136,
356        5700: 140,
357        5745: 149,
358        5765: 153,
359        5785: 157,
360        5805: 161,
361        5825: 165,
362    }
363
364    # All Wifi channels to frequencies lookup.
365    channel_2G_to_freq = {
366        1: 2412,
367        2: 2417,
368        3: 2422,
369        4: 2427,
370        5: 2432,
371        6: 2437,
372        7: 2442,
373        8: 2447,
374        9: 2452,
375        10: 2457,
376        11: 2462,
377        12: 2467,
378        13: 2472,
379        14: 2484
380    }
381
382    channel_5G_to_freq = {
383        183: 4915,
384        184: 4920,
385        185: 4925,
386        187: 4935,
387        188: 4940,
388        189: 4945,
389        192: 4960,
390        196: 4980,
391        7: 5035,
392        8: 5040,
393        9: 5045,
394        11: 5055,
395        12: 5060,
396        16: 5080,
397        34: 5170,
398        36: 5180,
399        38: 5190,
400        40: 5200,
401        42: 5210,
402        44: 5220,
403        46: 5230,
404        48: 5240,
405        52: 5260,
406        56: 5280,
407        60: 5300,
408        64: 5320,
409        100: 5500,
410        104: 5520,
411        108: 5540,
412        112: 5560,
413        116: 5580,
414        120: 5600,
415        124: 5620,
416        128: 5640,
417        132: 5660,
418        136: 5680,
419        140: 5700,
420        149: 5745,
421        153: 5765,
422        157: 5785,
423        161: 5805,
424        165: 5825
425    }
426
427
428class WifiChannelBase:
429    ALL_2G_FREQUENCIES = []
430    DFS_5G_FREQUENCIES = []
431    NONE_DFS_5G_FREQUENCIES = []
432    ALL_5G_FREQUENCIES = DFS_5G_FREQUENCIES + NONE_DFS_5G_FREQUENCIES
433    MIX_CHANNEL_SCAN = []
434
435    def band_to_freq(self, band):
436        _band_to_frequencies = {
437            WifiEnums.WIFI_BAND_24_GHZ: self.ALL_2G_FREQUENCIES,
438            WifiEnums.WIFI_BAND_5_GHZ: self.NONE_DFS_5G_FREQUENCIES,
439            WifiEnums.WIFI_BAND_5_GHZ_DFS_ONLY: self.DFS_5G_FREQUENCIES,
440            WifiEnums.WIFI_BAND_5_GHZ_WITH_DFS: self.ALL_5G_FREQUENCIES,
441            WifiEnums.WIFI_BAND_BOTH:
442            self.ALL_2G_FREQUENCIES + self.NONE_DFS_5G_FREQUENCIES,
443            WifiEnums.WIFI_BAND_BOTH_WITH_DFS:
444            self.ALL_5G_FREQUENCIES + self.ALL_2G_FREQUENCIES
445        }
446        return _band_to_frequencies[band]
447
448
449class WifiChannelUS(WifiChannelBase):
450    # US Wifi frequencies
451    ALL_2G_FREQUENCIES = [2412, 2417, 2422, 2427, 2432, 2437, 2442, 2447, 2452,
452                          2457, 2462]
453    NONE_DFS_5G_FREQUENCIES = [5180, 5200, 5220, 5240, 5745, 5765, 5785, 5805,
454                               5825]
455    MIX_CHANNEL_SCAN = [2412, 2437, 2462, 5180, 5200, 5280, 5260, 5300, 5500,
456                        5320, 5520, 5560, 5700, 5745, 5805]
457
458    def __init__(self, model=None):
459        self.DFS_5G_FREQUENCIES = [5260, 5280, 5300, 5320, 5500, 5520,
460                                   5540, 5560, 5580, 5600, 5620, 5640,
461                                   5660, 5680, 5700, 5720]
462        self.ALL_5G_FREQUENCIES = self.DFS_5G_FREQUENCIES + self.NONE_DFS_5G_FREQUENCIES
463
464
465class WifiReferenceNetworks:
466    """ Class to parse and return networks of different band and
467        auth type from reference_networks
468    """
469    def __init__(self, obj):
470        self.reference_networks = obj
471        self.WIFI_2G = "2g"
472        self.WIFI_5G = "5g"
473
474        self.secure_networks_2g = []
475        self.secure_networks_5g = []
476        self.open_networks_2g = []
477        self.open_networks_5g = []
478        self._parse_networks()
479
480    def _parse_networks(self):
481        for network in self.reference_networks:
482            for key in network:
483                if key == self.WIFI_2G:
484                    if "password" in network[key]:
485                        self.secure_networks_2g.append(network[key])
486                    else:
487                        self.open_networks_2g.append(network[key])
488                else:
489                    if "password" in network[key]:
490                        self.secure_networks_5g.append(network[key])
491                    else:
492                        self.open_networks_5g.append(network[key])
493
494    def return_2g_secure_networks(self):
495        return self.secure_networks_2g
496
497    def return_5g_secure_networks(self):
498        return self.secure_networks_5g
499
500    def return_2g_open_networks(self):
501        return self.open_networks_2g
502
503    def return_5g_open_networks(self):
504        return self.open_networks_5g
505
506    def return_secure_networks(self):
507        return self.secure_networks_2g + self.secure_networks_5g
508
509    def return_open_networks(self):
510        return self.open_networks_2g + self.open_networks_5g
511
512
513def _assert_on_fail_handler(func, assert_on_fail, *args, **kwargs):
514    """Wrapper function that handles the bahevior of assert_on_fail.
515
516    When assert_on_fail is True, let all test signals through, which can
517    terminate test cases directly. When assert_on_fail is False, the wrapper
518    raises no test signals and reports operation status by returning True or
519    False.
520
521    Args:
522        func: The function to wrap. This function reports operation status by
523              raising test signals.
524        assert_on_fail: A boolean that specifies if the output of the wrapper
525                        is test signal based or return value based.
526        args: Positional args for func.
527        kwargs: Name args for func.
528
529    Returns:
530        If assert_on_fail is True, returns True/False to signal operation
531        status, otherwise return nothing.
532    """
533    try:
534        func(*args, **kwargs)
535        if not assert_on_fail:
536            return True
537    except signals.TestSignal:
538        if assert_on_fail:
539            raise
540        return False
541
542
543def assert_network_in_list(target, network_list):
544    """Makes sure a specified target Wi-Fi network exists in a list of Wi-Fi
545    networks.
546
547    Args:
548        target: A dict representing a Wi-Fi network.
549                E.g. {WifiEnums.SSID_KEY: "SomeNetwork"}
550        network_list: A list of dicts, each representing a Wi-Fi network.
551    """
552    match_results = match_networks(target, network_list)
553    asserts.assert_true(
554        match_results, "Target network %s, does not exist in network list %s" %
555        (target, network_list))
556
557
558def match_networks(target_params, networks):
559    """Finds the WiFi networks that match a given set of parameters in a list
560    of WiFi networks.
561
562    To be considered a match, the network should contain every key-value pair
563    of target_params
564
565    Args:
566        target_params: A dict with 1 or more key-value pairs representing a Wi-Fi network.
567                       E.g { 'SSID': 'wh_ap1_5g', 'BSSID': '30:b5:c2:33:e4:47' }
568        networks: A list of dict objects representing WiFi networks.
569
570    Returns:
571        The networks that match the target parameters.
572    """
573    results = []
574    asserts.assert_true(target_params,
575                        "Expected networks object 'target_params' is empty")
576    for n in networks:
577        add_network = 1
578        for k, v in target_params.items():
579            if k not in n:
580                add_network = 0
581                break
582            if n[k] != v:
583                add_network = 0
584                break
585        if add_network:
586            results.append(n)
587    return results
588
589
590def wait_for_wifi_state(ad, state, assert_on_fail=True):
591    """Waits for the device to transition to the specified wifi state
592
593    Args:
594        ad: An AndroidDevice object.
595        state: Wifi state to wait for.
596        assert_on_fail: If True, error checks in this function will raise test
597                        failure signals.
598
599    Returns:
600        If assert_on_fail is False, function returns True if the device transitions
601        to the specified state, False otherwise. If assert_on_fail is True, no return value.
602    """
603    return _assert_on_fail_handler(
604        _wait_for_wifi_state, assert_on_fail, ad, state=state)
605
606
607def _wait_for_wifi_state(ad, state):
608    """Toggles the state of wifi.
609
610    TestFailure signals are raised when something goes wrong.
611
612    Args:
613        ad: An AndroidDevice object.
614        state: Wifi state to wait for.
615    """
616    if state == ad.droid.wifiCheckState():
617        # Check if the state is already achieved, so we don't wait for the
618        # state change event by mistake.
619        return
620    ad.droid.wifiStartTrackingStateChange()
621    fail_msg = "Device did not transition to Wi-Fi state to %s on %s." % (state,
622                                                           ad.serial)
623    try:
624        ad.ed.wait_for_event(wifi_constants.WIFI_STATE_CHANGED,
625                             lambda x: x["data"]["enabled"] == state,
626                             SHORT_TIMEOUT)
627    except Empty:
628        asserts.assert_equal(state, ad.droid.wifiCheckState(), fail_msg)
629    finally:
630        ad.droid.wifiStopTrackingStateChange()
631
632
633def wifi_toggle_state(ad, new_state=None, assert_on_fail=True):
634    """Toggles the state of wifi.
635
636    Args:
637        ad: An AndroidDevice object.
638        new_state: Wifi state to set to. If None, opposite of the current state.
639        assert_on_fail: If True, error checks in this function will raise test
640                        failure signals.
641
642    Returns:
643        If assert_on_fail is False, function returns True if the toggle was
644        successful, False otherwise. If assert_on_fail is True, no return value.
645    """
646    return _assert_on_fail_handler(
647        _wifi_toggle_state, assert_on_fail, ad, new_state=new_state)
648
649
650def _wifi_toggle_state(ad, new_state=None):
651    """Toggles the state of wifi.
652
653    TestFailure signals are raised when something goes wrong.
654
655    Args:
656        ad: An AndroidDevice object.
657        new_state: The state to set Wi-Fi to. If None, opposite of the current
658                   state will be set.
659    """
660    if new_state is None:
661        new_state = not ad.droid.wifiCheckState()
662    elif new_state == ad.droid.wifiCheckState():
663        # Check if the new_state is already achieved, so we don't wait for the
664        # state change event by mistake.
665        return
666    ad.droid.wifiStartTrackingStateChange()
667    ad.log.info("Setting Wi-Fi state to %s.", new_state)
668    ad.ed.clear_all_events()
669    # Setting wifi state.
670    ad.droid.wifiToggleState(new_state)
671    time.sleep(2)
672    fail_msg = "Failed to set Wi-Fi state to %s on %s." % (new_state,
673                                                           ad.serial)
674    try:
675        ad.ed.wait_for_event(wifi_constants.WIFI_STATE_CHANGED,
676                             lambda x: x["data"]["enabled"] == new_state,
677                             SHORT_TIMEOUT)
678    except Empty:
679        asserts.assert_equal(new_state, ad.droid.wifiCheckState(), fail_msg)
680    finally:
681        ad.droid.wifiStopTrackingStateChange()
682
683
684def reset_wifi(ad):
685    """Clears all saved Wi-Fi networks on a device.
686
687    This will turn Wi-Fi on.
688
689    Args:
690        ad: An AndroidDevice object.
691
692    """
693    networks = ad.droid.wifiGetConfiguredNetworks()
694    if not networks:
695        return
696    for n in networks:
697        ad.droid.wifiForgetNetwork(n['networkId'])
698        try:
699            event = ad.ed.pop_event(wifi_constants.WIFI_FORGET_NW_SUCCESS,
700                                    SHORT_TIMEOUT)
701        except Empty:
702            logging.warning("Could not confirm the removal of network %s.", n)
703    # Check again to see if there's any network left.
704    asserts.assert_true(
705        not ad.droid.wifiGetConfiguredNetworks(),
706        "Failed to remove these configured Wi-Fi networks: %s" % networks)
707
708
709def toggle_airplane_mode_on_and_off(ad):
710    """Turn ON and OFF Airplane mode.
711
712    ad: An AndroidDevice object.
713    Returns: Assert if turning on/off Airplane mode fails.
714
715    """
716    ad.log.debug("Toggling Airplane mode ON.")
717    asserts.assert_true(
718        utils.force_airplane_mode(ad, True),
719        "Can not turn on airplane mode on: %s" % ad.serial)
720    time.sleep(DEFAULT_TIMEOUT)
721    ad.log.debug("Toggling Airplane mode OFF.")
722    asserts.assert_true(
723        utils.force_airplane_mode(ad, False),
724        "Can not turn on airplane mode on: %s" % ad.serial)
725    time.sleep(DEFAULT_TIMEOUT)
726
727
728def toggle_wifi_off_and_on(ad):
729    """Turn OFF and ON WiFi.
730
731    ad: An AndroidDevice object.
732    Returns: Assert if turning off/on WiFi fails.
733
734    """
735    ad.log.debug("Toggling wifi OFF.")
736    wifi_toggle_state(ad, False)
737    time.sleep(DEFAULT_TIMEOUT)
738    ad.log.debug("Toggling wifi ON.")
739    wifi_toggle_state(ad, True)
740    time.sleep(DEFAULT_TIMEOUT)
741
742
743def wifi_forget_network(ad, net_ssid):
744    """Remove configured Wifi network on an android device.
745
746    Args:
747        ad: android_device object for forget network.
748        net_ssid: ssid of network to be forget
749
750    """
751    networks = ad.droid.wifiGetConfiguredNetworks()
752    if not networks:
753        return
754    for n in networks:
755        if net_ssid in n[WifiEnums.SSID_KEY]:
756            ad.droid.wifiForgetNetwork(n['networkId'])
757            try:
758                event = ad.ed.pop_event(wifi_constants.WIFI_FORGET_NW_SUCCESS,
759                                        SHORT_TIMEOUT)
760            except Empty:
761                asserts.fail("Failed to remove network %s." % n)
762
763
764def wifi_test_device_init(ad):
765    """Initializes an android device for wifi testing.
766
767    0. Make sure SL4A connection is established on the android device.
768    1. Disable location service's WiFi scan.
769    2. Turn WiFi on.
770    3. Clear all saved networks.
771    4. Set country code to US.
772    5. Enable WiFi verbose logging.
773    6. Sync device time with computer time.
774    7. Turn off cellular data.
775    8. Turn off ambient display.
776    """
777    utils.require_sl4a((ad, ))
778    ad.droid.wifiScannerToggleAlwaysAvailable(False)
779    msg = "Failed to turn off location service's scan."
780    asserts.assert_true(not ad.droid.wifiScannerIsAlwaysAvailable(), msg)
781    wifi_toggle_state(ad, True)
782    reset_wifi(ad)
783    ad.droid.wifiEnableVerboseLogging(1)
784    msg = "Failed to enable WiFi verbose logging."
785    asserts.assert_equal(ad.droid.wifiGetVerboseLoggingLevel(), 1, msg)
786    # We don't verify the following settings since they are not critical.
787    # Set wpa_supplicant log level to EXCESSIVE.
788    output = ad.adb.shell("wpa_cli -i wlan0 -p -g@android:wpa_wlan0 IFNAME="
789                          "wlan0 log_level EXCESSIVE")
790    ad.log.info("wpa_supplicant log change status: %s", output)
791    utils.sync_device_time(ad)
792    ad.droid.telephonyToggleDataConnection(False)
793    set_wifi_country_code(ad, WifiEnums.CountryCode.US)
794    utils.set_ambient_display(ad, False)
795
796def set_wifi_country_code(ad, country_code):
797    """Sets the wifi country code on the device.
798
799    Args:
800        ad: An AndroidDevice object.
801        country_code: 2 letter ISO country code
802
803    Raises:
804        An RpcException if unable to set the country code.
805    """
806    try:
807        ad.adb.shell("cmd wifi force-country-code enabled %s" % country_code)
808    except ad.adb.AdbError as e:
809        ad.droid.wifiSetCountryCode(WifiEnums.CountryCode.US)
810
811
812def start_wifi_connection_scan(ad):
813    """Starts a wifi connection scan and wait for results to become available.
814
815    Args:
816        ad: An AndroidDevice object.
817    """
818    ad.ed.clear_all_events()
819    ad.droid.wifiStartScan()
820    try:
821        ad.ed.pop_event("WifiManagerScanResultsAvailable", 60)
822    except Empty:
823        asserts.fail("Wi-Fi results did not become available within 60s.")
824
825
826def start_wifi_connection_scan_and_return_status(ad):
827    """
828    Starts a wifi connection scan and wait for results to become available
829    or a scan failure to be reported.
830
831    Args:
832        ad: An AndroidDevice object.
833    Returns:
834        True: if scan succeeded & results are available
835        False: if scan failed
836    """
837    ad.ed.clear_all_events()
838    ad.droid.wifiStartScan()
839    try:
840        events = ad.ed.pop_events(
841            "WifiManagerScan(ResultsAvailable|Failure)", 60)
842    except Empty:
843        asserts.fail(
844            "Wi-Fi scan results/failure did not become available within 60s.")
845    # If there are multiple matches, we check for atleast one success.
846    for event in events:
847        if event["name"] == "WifiManagerScanResultsAvailable":
848            return True
849        elif event["name"] == "WifiManagerScanFailure":
850            ad.log.debug("Scan failure received")
851    return False
852
853
854def start_wifi_connection_scan_and_check_for_network(ad, network_ssid,
855                                                     max_tries=3):
856    """
857    Start connectivity scans & checks if the |network_ssid| is seen in
858    scan results. The method performs a max of |max_tries| connectivity scans
859    to find the network.
860
861    Args:
862        ad: An AndroidDevice object.
863        network_ssid: SSID of the network we are looking for.
864        max_tries: Number of scans to try.
865    Returns:
866        True: if network_ssid is found in scan results.
867        False: if network_ssid is not found in scan results.
868    """
869    for num_tries in range(max_tries):
870        if start_wifi_connection_scan_and_return_status(ad):
871            scan_results = ad.droid.wifiGetScanResults()
872            match_results = match_networks(
873                {WifiEnums.SSID_KEY: network_ssid}, scan_results)
874            if len(match_results) > 0:
875                return True
876    return False
877
878
879def start_wifi_connection_scan_and_ensure_network_found(ad, network_ssid,
880                                                        max_tries=3):
881    """
882    Start connectivity scans & ensure the |network_ssid| is seen in
883    scan results. The method performs a max of |max_tries| connectivity scans
884    to find the network.
885    This method asserts on failure!
886
887    Args:
888        ad: An AndroidDevice object.
889        network_ssid: SSID of the network we are looking for.
890        max_tries: Number of scans to try.
891    """
892    ad.log.info("Starting scans to ensure %s is present", network_ssid)
893    assert_msg = "Failed to find " + network_ssid + " in scan results" \
894        " after " + str(max_tries) + " tries"
895    asserts.assert_true(start_wifi_connection_scan_and_check_for_network(
896        ad, network_ssid, max_tries), assert_msg)
897
898
899def start_wifi_connection_scan_and_ensure_network_not_found(ad, network_ssid,
900                                                            max_tries=3):
901    """
902    Start connectivity scans & ensure the |network_ssid| is not seen in
903    scan results. The method performs a max of |max_tries| connectivity scans
904    to find the network.
905    This method asserts on failure!
906
907    Args:
908        ad: An AndroidDevice object.
909        network_ssid: SSID of the network we are looking for.
910        max_tries: Number of scans to try.
911    """
912    ad.log.info("Starting scans to ensure %s is not present", network_ssid)
913    assert_msg = "Found " + network_ssid + " in scan results" \
914        " after " + str(max_tries) + " tries"
915    asserts.assert_false(start_wifi_connection_scan_and_check_for_network(
916        ad, network_ssid, max_tries), assert_msg)
917
918
919def start_wifi_background_scan(ad, scan_setting):
920    """Starts wifi background scan.
921
922    Args:
923        ad: android_device object to initiate connection on.
924        scan_setting: A dict representing the settings of the scan.
925
926    Returns:
927        If scan was started successfully, event data of success event is returned.
928    """
929    idx = ad.droid.wifiScannerStartBackgroundScan(scan_setting)
930    event = ad.ed.pop_event("WifiScannerScan{}onSuccess".format(idx),
931                            SHORT_TIMEOUT)
932    return event['data']
933
934
935def start_wifi_tethering(ad, ssid, password, band=None, hidden=None):
936    """Starts wifi tethering on an android_device.
937
938    Args:
939        ad: android_device to start wifi tethering on.
940        ssid: The SSID the soft AP should broadcast.
941        password: The password the soft AP should use.
942        band: The band the soft AP should be set on. It should be either
943            WifiEnums.WIFI_CONFIG_APBAND_2G or WifiEnums.WIFI_CONFIG_APBAND_5G.
944        hidden: boolean to indicate if the AP needs to be hidden or not.
945
946    Returns:
947        No return value. Error checks in this function will raise test failure signals
948    """
949    config = {WifiEnums.SSID_KEY: ssid}
950    if password:
951        config[WifiEnums.PWD_KEY] = password
952    if band:
953        config[WifiEnums.AP_BAND_KEY] = band
954    if hidden:
955      config[WifiEnums.HIDDEN_KEY] = hidden
956    asserts.assert_true(
957        ad.droid.wifiSetWifiApConfiguration(config),
958        "Failed to update WifiAp Configuration")
959    ad.droid.wifiStartTrackingTetherStateChange()
960    ad.droid.connectivityStartTethering(tel_defines.TETHERING_WIFI, False)
961    try:
962        ad.ed.pop_event("ConnectivityManagerOnTetheringStarted")
963        ad.ed.wait_for_event("TetherStateChanged",
964                             lambda x: x["data"]["ACTIVE_TETHER"], 30)
965        ad.log.debug("Tethering started successfully.")
966    except Empty:
967        msg = "Failed to receive confirmation of wifi tethering starting"
968        asserts.fail(msg)
969    finally:
970        ad.droid.wifiStopTrackingTetherStateChange()
971
972
973def save_wifi_soft_ap_config(ad, wifi_config, band=None, hidden=None,
974                             security=None, password=None,
975                             channel=None, max_clients=None,
976                             shutdown_timeout_enable=None,
977                             shutdown_timeout_millis=None,
978                             client_control_enable=None,
979                             allowedList=None, blockedList=None):
980    """ Save a soft ap configuration and verified
981    Args:
982        ad: android_device to set soft ap configuration.
983        wifi_config: a soft ap configuration object, at least include SSID.
984        band: specifies the band for the soft ap.
985        hidden: specifies the soft ap need to broadcast its SSID or not.
986        security: specifies the security type for the soft ap.
987        password: specifies the password for the soft ap.
988        channel: specifies the channel for the soft ap.
989        max_clients: specifies the maximum connected client number.
990        shutdown_timeout_enable: specifies the auto shut down enable or not.
991        shutdown_timeout_millis: specifies the shut down timeout value.
992        client_control_enable: specifies the client control enable or not.
993        allowedList: specifies allowed clients list.
994        blockedList: specifies blocked clients list.
995    """
996    if security and password:
997       wifi_config[WifiEnums.SECURITY] = security
998       wifi_config[WifiEnums.PWD_KEY] = password
999    if band:
1000        wifi_config[WifiEnums.AP_BAND_KEY] = band
1001    if hidden:
1002        wifi_config[WifiEnums.HIDDEN_KEY] = hidden
1003    if channel and band:
1004        wifi_config[WifiEnums.AP_BAND_KEY] = band
1005        wifi_config[WifiEnums.AP_CHANNEL_KEY] = channel
1006    if max_clients:
1007        wifi_config[WifiEnums.AP_MAXCLIENTS_KEY] = max_clients
1008    if shutdown_timeout_enable:
1009        wifi_config[
1010            WifiEnums.AP_SHUTDOWNTIMEOUTENABLE_KEY] = shutdown_timeout_enable
1011    if shutdown_timeout_millis:
1012        wifi_config[
1013            WifiEnums.AP_SHUTDOWNTIMEOUT_KEY] = shutdown_timeout_millis
1014    if client_control_enable:
1015        wifi_config[WifiEnums.AP_CLIENTCONTROL_KEY] = client_control_enable
1016    if allowedList:
1017        wifi_config[WifiEnums.AP_ALLOWEDLIST_KEY] = allowedList
1018    if blockedList:
1019        wifi_config[WifiEnums.AP_BLOCKEDLIST_KEY] = blockedList
1020
1021    if WifiEnums.AP_CHANNEL_KEY in wifi_config and wifi_config[
1022        WifiEnums.AP_CHANNEL_KEY] == 0:
1023        del wifi_config[WifiEnums.AP_CHANNEL_KEY]
1024
1025    if WifiEnums.SECURITY in wifi_config and wifi_config[
1026        WifiEnums.SECURITY] == WifiEnums.SoftApSecurityType.OPEN:
1027        del wifi_config[WifiEnums.SECURITY]
1028        del wifi_config[WifiEnums.PWD_KEY]
1029
1030    asserts.assert_true(ad.droid.wifiSetWifiApConfiguration(wifi_config),
1031                        "Failed to set WifiAp Configuration")
1032
1033    wifi_ap = ad.droid.wifiGetApConfiguration()
1034    asserts.assert_true(
1035        wifi_ap[WifiEnums.SSID_KEY] == wifi_config[WifiEnums.SSID_KEY],
1036        "Hotspot SSID doesn't match")
1037    if WifiEnums.SECURITY in wifi_config:
1038        asserts.assert_true(
1039            wifi_ap[WifiEnums.SECURITY] == wifi_config[WifiEnums.SECURITY],
1040            "Hotspot Security doesn't match")
1041    if WifiEnums.PWD_KEY in wifi_config:
1042        asserts.assert_true(
1043            wifi_ap[WifiEnums.PWD_KEY] == wifi_config[WifiEnums.PWD_KEY],
1044            "Hotspot Password doesn't match")
1045
1046    if WifiEnums.HIDDEN_KEY in wifi_config:
1047        asserts.assert_true(
1048            wifi_ap[WifiEnums.HIDDEN_KEY] == wifi_config[WifiEnums.HIDDEN_KEY],
1049            "Hotspot hidden setting doesn't match")
1050
1051    if WifiEnums.AP_BAND_KEY in wifi_config:
1052        asserts.assert_true(
1053            wifi_ap[WifiEnums.AP_BAND_KEY] == wifi_config[WifiEnums.AP_BAND_KEY],
1054            "Hotspot Band doesn't match")
1055    if WifiEnums.AP_CHANNEL_KEY in wifi_config:
1056        asserts.assert_true(
1057            wifi_ap[WifiEnums.AP_CHANNEL_KEY] == wifi_config[
1058            WifiEnums.AP_CHANNEL_KEY], "Hotspot Channel doesn't match")
1059    if WifiEnums.AP_MAXCLIENTS_KEY in wifi_config:
1060        asserts.assert_true(
1061            wifi_ap[WifiEnums.AP_MAXCLIENTS_KEY] == wifi_config[
1062            WifiEnums.AP_MAXCLIENTS_KEY], "Hotspot Max Clients doesn't match")
1063    if WifiEnums.AP_SHUTDOWNTIMEOUTENABLE_KEY in wifi_config:
1064        asserts.assert_true(
1065            wifi_ap[WifiEnums.AP_SHUTDOWNTIMEOUTENABLE_KEY] == wifi_config[
1066            WifiEnums.AP_SHUTDOWNTIMEOUTENABLE_KEY],
1067            "Hotspot ShutDown feature flag doesn't match")
1068    if WifiEnums.AP_SHUTDOWNTIMEOUT_KEY in wifi_config:
1069        asserts.assert_true(
1070            wifi_ap[WifiEnums.AP_SHUTDOWNTIMEOUT_KEY] == wifi_config[
1071            WifiEnums.AP_SHUTDOWNTIMEOUT_KEY],
1072            "Hotspot ShutDown timeout setting doesn't match")
1073    if WifiEnums.AP_CLIENTCONTROL_KEY in wifi_config:
1074        asserts.assert_true(
1075            wifi_ap[WifiEnums.AP_CLIENTCONTROL_KEY] == wifi_config[
1076            WifiEnums.AP_CLIENTCONTROL_KEY],
1077            "Hotspot Client control flag doesn't match")
1078    if WifiEnums.AP_ALLOWEDLIST_KEY in wifi_config:
1079        asserts.assert_true(
1080            wifi_ap[WifiEnums.AP_ALLOWEDLIST_KEY] == wifi_config[
1081            WifiEnums.AP_ALLOWEDLIST_KEY],
1082            "Hotspot Allowed List doesn't match")
1083    if WifiEnums.AP_BLOCKEDLIST_KEY in wifi_config:
1084        asserts.assert_true(
1085            wifi_ap[WifiEnums.AP_BLOCKEDLIST_KEY] == wifi_config[
1086            WifiEnums.AP_BLOCKEDLIST_KEY],
1087            "Hotspot Blocked List doesn't match")
1088
1089def start_wifi_tethering_saved_config(ad):
1090    """ Turn on wifi hotspot with a config that is already saved """
1091    ad.droid.wifiStartTrackingTetherStateChange()
1092    ad.droid.connectivityStartTethering(tel_defines.TETHERING_WIFI, False)
1093    try:
1094        ad.ed.pop_event("ConnectivityManagerOnTetheringStarted")
1095        ad.ed.wait_for_event("TetherStateChanged",
1096                             lambda x: x["data"]["ACTIVE_TETHER"], 30)
1097    except:
1098        asserts.fail("Didn't receive wifi tethering starting confirmation")
1099    finally:
1100        ad.droid.wifiStopTrackingTetherStateChange()
1101
1102
1103def stop_wifi_tethering(ad):
1104    """Stops wifi tethering on an android_device.
1105    Args:
1106        ad: android_device to stop wifi tethering on.
1107    """
1108    ad.droid.wifiStartTrackingTetherStateChange()
1109    ad.droid.connectivityStopTethering(tel_defines.TETHERING_WIFI)
1110    try:
1111        ad.ed.pop_event("WifiManagerApDisabled", 30)
1112        ad.ed.wait_for_event("TetherStateChanged",
1113                             lambda x: not x["data"]["ACTIVE_TETHER"], 30)
1114    except Empty:
1115        msg = "Failed to receive confirmation of wifi tethering stopping"
1116        asserts.fail(msg)
1117    finally:
1118        ad.droid.wifiStopTrackingTetherStateChange()
1119
1120
1121def toggle_wifi_and_wait_for_reconnection(ad,
1122                                          network,
1123                                          num_of_tries=1,
1124                                          assert_on_fail=True):
1125    """Toggle wifi state and then wait for Android device to reconnect to
1126    the provided wifi network.
1127
1128    This expects the device to be already connected to the provided network.
1129
1130    Logic steps are
1131     1. Ensure that we're connected to the network.
1132     2. Turn wifi off.
1133     3. Wait for 10 seconds.
1134     4. Turn wifi on.
1135     5. Wait for the "connected" event, then confirm the connected ssid is the
1136        one requested.
1137
1138    Args:
1139        ad: android_device object to initiate connection on.
1140        network: A dictionary representing the network to await connection. The
1141                 dictionary must have the key "SSID".
1142        num_of_tries: An integer that is the number of times to try before
1143                      delaring failure. Default is 1.
1144        assert_on_fail: If True, error checks in this function will raise test
1145                        failure signals.
1146
1147    Returns:
1148        If assert_on_fail is False, function returns True if the toggle was
1149        successful, False otherwise. If assert_on_fail is True, no return value.
1150    """
1151    return _assert_on_fail_handler(
1152        _toggle_wifi_and_wait_for_reconnection,
1153        assert_on_fail,
1154        ad,
1155        network,
1156        num_of_tries=num_of_tries)
1157
1158
1159def _toggle_wifi_and_wait_for_reconnection(ad, network, num_of_tries=3):
1160    """Toggle wifi state and then wait for Android device to reconnect to
1161    the provided wifi network.
1162
1163    This expects the device to be already connected to the provided network.
1164
1165    Logic steps are
1166     1. Ensure that we're connected to the network.
1167     2. Turn wifi off.
1168     3. Wait for 10 seconds.
1169     4. Turn wifi on.
1170     5. Wait for the "connected" event, then confirm the connected ssid is the
1171        one requested.
1172
1173    This will directly fail a test if anything goes wrong.
1174
1175    Args:
1176        ad: android_device object to initiate connection on.
1177        network: A dictionary representing the network to await connection. The
1178                 dictionary must have the key "SSID".
1179        num_of_tries: An integer that is the number of times to try before
1180                      delaring failure. Default is 1.
1181    """
1182    expected_ssid = network[WifiEnums.SSID_KEY]
1183    # First ensure that we're already connected to the provided network.
1184    verify_con = {WifiEnums.SSID_KEY: expected_ssid}
1185    verify_wifi_connection_info(ad, verify_con)
1186    # Now toggle wifi state and wait for the connection event.
1187    wifi_toggle_state(ad, False)
1188    time.sleep(10)
1189    wifi_toggle_state(ad, True)
1190    ad.droid.wifiStartTrackingStateChange()
1191    try:
1192        connect_result = None
1193        for i in range(num_of_tries):
1194            try:
1195                connect_result = ad.ed.pop_event(wifi_constants.WIFI_CONNECTED,
1196                                                 30)
1197                break
1198            except Empty:
1199                pass
1200        asserts.assert_true(connect_result,
1201                            "Failed to connect to Wi-Fi network %s on %s" %
1202                            (network, ad.serial))
1203        logging.debug("Connection result on %s: %s.", ad.serial,
1204                      connect_result)
1205        actual_ssid = connect_result['data'][WifiEnums.SSID_KEY]
1206        asserts.assert_equal(actual_ssid, expected_ssid,
1207                             "Connected to the wrong network on %s."
1208                             "Expected %s, but got %s." %
1209                             (ad.serial, expected_ssid, actual_ssid))
1210        logging.info("Connected to Wi-Fi network %s on %s", actual_ssid,
1211                     ad.serial)
1212    finally:
1213        ad.droid.wifiStopTrackingStateChange()
1214
1215
1216def wait_for_connect(ad, expected_ssid=None, expected_id=None, tries=2,
1217                     assert_on_fail=True):
1218    """Wait for a connect event.
1219
1220    This will directly fail a test if anything goes wrong.
1221
1222    Args:
1223        ad: An Android device object.
1224        expected_ssid: SSID of the network to connect to.
1225        expected_id: Network Id of the network to connect to.
1226        tries: An integer that is the number of times to try before failing.
1227        assert_on_fail: If True, error checks in this function will raise test
1228                        failure signals.
1229
1230    Returns:
1231        Returns a value only if assert_on_fail is false.
1232        Returns True if the connection was successful, False otherwise.
1233    """
1234    return _assert_on_fail_handler(
1235        _wait_for_connect, assert_on_fail, ad, expected_ssid, expected_id,
1236        tries)
1237
1238
1239def _wait_for_connect(ad, expected_ssid=None, expected_id=None, tries=2):
1240    """Wait for a connect event.
1241
1242    Args:
1243        ad: An Android device object.
1244        expected_ssid: SSID of the network to connect to.
1245        expected_id: Network Id of the network to connect to.
1246        tries: An integer that is the number of times to try before failing.
1247    """
1248    ad.droid.wifiStartTrackingStateChange()
1249    try:
1250        connect_result = _wait_for_connect_event(
1251            ad, ssid=expected_ssid, id=expected_id, tries=tries)
1252        asserts.assert_true(connect_result,
1253                            "Failed to connect to Wi-Fi network %s" %
1254                            expected_ssid)
1255        ad.log.debug("Wi-Fi connection result: %s.", connect_result)
1256        actual_ssid = connect_result['data'][WifiEnums.SSID_KEY]
1257        if expected_ssid:
1258            asserts.assert_equal(actual_ssid, expected_ssid,
1259                                 "Connected to the wrong network")
1260        actual_id = connect_result['data'][WifiEnums.NETID_KEY]
1261        if expected_id:
1262            asserts.assert_equal(actual_id, expected_id,
1263                                 "Connected to the wrong network")
1264        ad.log.info("Connected to Wi-Fi network %s.", actual_ssid)
1265    except Empty:
1266        asserts.fail("Failed to start connection process to %s" %
1267                     expected_ssid)
1268    except Exception as error:
1269        ad.log.error("Failed to connect to %s with error %s", expected_ssid,
1270                     error)
1271        raise signals.TestFailure("Failed to connect to %s network" %
1272                                  expected_ssid)
1273    finally:
1274        ad.droid.wifiStopTrackingStateChange()
1275
1276
1277def _wait_for_connect_event(ad, ssid=None, id=None, tries=1):
1278    """Wait for a connect event on queue and pop when available.
1279
1280    Args:
1281        ad: An Android device object.
1282        ssid: SSID of the network to connect to.
1283        id: Network Id of the network to connect to.
1284        tries: An integer that is the number of times to try before failing.
1285
1286    Returns:
1287        A dict with details of the connection data, which looks like this:
1288        {
1289         'time': 1485460337798,
1290         'name': 'WifiNetworkConnected',
1291         'data': {
1292                  'rssi': -27,
1293                  'is_24ghz': True,
1294                  'mac_address': '02:00:00:00:00:00',
1295                  'network_id': 1,
1296                  'BSSID': '30:b5:c2:33:d3:fc',
1297                  'ip_address': 117483712,
1298                  'link_speed': 54,
1299                  'supplicant_state': 'completed',
1300                  'hidden_ssid': False,
1301                  'SSID': 'wh_ap1_2g',
1302                  'is_5ghz': False}
1303        }
1304
1305    """
1306    conn_result = None
1307
1308    # If ssid and network id is None, just wait for any connect event.
1309    if id is None and ssid is None:
1310        for i in range(tries):
1311            try:
1312                conn_result = ad.ed.pop_event(wifi_constants.WIFI_CONNECTED, 30)
1313                break
1314            except Empty:
1315                pass
1316    else:
1317    # If ssid or network id is specified, wait for specific connect event.
1318        for i in range(tries):
1319            try:
1320                conn_result = ad.ed.pop_event(wifi_constants.WIFI_CONNECTED, 30)
1321                if id and conn_result['data'][WifiEnums.NETID_KEY] == id:
1322                    break
1323                elif ssid and conn_result['data'][WifiEnums.SSID_KEY] == ssid:
1324                    break
1325            except Empty:
1326                pass
1327
1328    return conn_result
1329
1330
1331def wait_for_disconnect(ad, timeout=10):
1332    """Wait for a disconnect event within the specified timeout.
1333
1334    Args:
1335        ad: Android device object.
1336        timeout: Timeout in seconds.
1337
1338    """
1339    try:
1340        ad.droid.wifiStartTrackingStateChange()
1341        event = ad.ed.pop_event("WifiNetworkDisconnected", timeout)
1342    except Empty:
1343        raise signals.TestFailure("Device did not disconnect from the network")
1344    finally:
1345        ad.droid.wifiStopTrackingStateChange()
1346
1347
1348def ensure_no_disconnect(ad, duration=10):
1349    """Ensure that there is no disconnect for the specified duration.
1350
1351    Args:
1352        ad: Android device object.
1353        duration: Duration in seconds.
1354
1355    """
1356    try:
1357        ad.droid.wifiStartTrackingStateChange()
1358        event = ad.ed.pop_event("WifiNetworkDisconnected", duration)
1359        raise signals.TestFailure("Device disconnected from the network")
1360    except Empty:
1361        pass
1362    finally:
1363        ad.droid.wifiStopTrackingStateChange()
1364
1365
1366def connect_to_wifi_network(ad, network, assert_on_fail=True,
1367        check_connectivity=True, hidden=False):
1368    """Connection logic for open and psk wifi networks.
1369
1370    Args:
1371        ad: AndroidDevice to use for connection
1372        network: network info of the network to connect to
1373        assert_on_fail: If true, errors from wifi_connect will raise
1374                        test failure signals.
1375        hidden: Is the Wifi network hidden.
1376    """
1377    if hidden:
1378        start_wifi_connection_scan_and_ensure_network_not_found(
1379            ad, network[WifiEnums.SSID_KEY])
1380    else:
1381        start_wifi_connection_scan_and_ensure_network_found(
1382            ad, network[WifiEnums.SSID_KEY])
1383    wifi_connect(ad,
1384                 network,
1385                 num_of_tries=3,
1386                 assert_on_fail=assert_on_fail,
1387                 check_connectivity=check_connectivity)
1388
1389
1390def connect_to_wifi_network_with_id(ad, network_id, network_ssid):
1391    """Connect to the given network using network id and verify SSID.
1392
1393    Args:
1394        network_id: int Network Id of the network.
1395        network_ssid: string SSID of the network.
1396
1397    Returns: True if connect using network id was successful;
1398             False otherwise.
1399
1400    """
1401    start_wifi_connection_scan_and_ensure_network_found(ad, network_ssid)
1402    wifi_connect_by_id(ad, network_id)
1403    connect_data = ad.droid.wifiGetConnectionInfo()
1404    connect_ssid = connect_data[WifiEnums.SSID_KEY]
1405    ad.log.debug("Expected SSID = %s Connected SSID = %s" %
1406                   (network_ssid, connect_ssid))
1407    if connect_ssid != network_ssid:
1408        return False
1409    return True
1410
1411
1412def wifi_connect(ad, network, num_of_tries=1, assert_on_fail=True,
1413        check_connectivity=True):
1414    """Connect an Android device to a wifi network.
1415
1416    Initiate connection to a wifi network, wait for the "connected" event, then
1417    confirm the connected ssid is the one requested.
1418
1419    This will directly fail a test if anything goes wrong.
1420
1421    Args:
1422        ad: android_device object to initiate connection on.
1423        network: A dictionary representing the network to connect to. The
1424                 dictionary must have the key "SSID".
1425        num_of_tries: An integer that is the number of times to try before
1426                      delaring failure. Default is 1.
1427        assert_on_fail: If True, error checks in this function will raise test
1428                        failure signals.
1429
1430    Returns:
1431        Returns a value only if assert_on_fail is false.
1432        Returns True if the connection was successful, False otherwise.
1433    """
1434    return _assert_on_fail_handler(
1435        _wifi_connect, assert_on_fail, ad, network, num_of_tries=num_of_tries,
1436          check_connectivity=check_connectivity)
1437
1438
1439def _wifi_connect(ad, network, num_of_tries=1, check_connectivity=True):
1440    """Connect an Android device to a wifi network.
1441
1442    Initiate connection to a wifi network, wait for the "connected" event, then
1443    confirm the connected ssid is the one requested.
1444
1445    This will directly fail a test if anything goes wrong.
1446
1447    Args:
1448        ad: android_device object to initiate connection on.
1449        network: A dictionary representing the network to connect to. The
1450                 dictionary must have the key "SSID".
1451        num_of_tries: An integer that is the number of times to try before
1452                      delaring failure. Default is 1.
1453    """
1454    asserts.assert_true(WifiEnums.SSID_KEY in network,
1455                        "Key '%s' must be present in network definition." %
1456                        WifiEnums.SSID_KEY)
1457    ad.droid.wifiStartTrackingStateChange()
1458    expected_ssid = network[WifiEnums.SSID_KEY]
1459    ad.droid.wifiConnectByConfig(network)
1460    ad.log.info("Starting connection process to %s", expected_ssid)
1461    try:
1462        event = ad.ed.pop_event(wifi_constants.CONNECT_BY_CONFIG_SUCCESS, 30)
1463        connect_result = _wait_for_connect_event(
1464            ad, ssid=expected_ssid, tries=num_of_tries)
1465        asserts.assert_true(connect_result,
1466                            "Failed to connect to Wi-Fi network %s on %s" %
1467                            (network, ad.serial))
1468        ad.log.debug("Wi-Fi connection result: %s.", connect_result)
1469        actual_ssid = connect_result['data'][WifiEnums.SSID_KEY]
1470        asserts.assert_equal(actual_ssid, expected_ssid,
1471                             "Connected to the wrong network on %s." %
1472                             ad.serial)
1473        ad.log.info("Connected to Wi-Fi network %s.", actual_ssid)
1474
1475        if check_connectivity:
1476            internet = validate_connection(ad, DEFAULT_PING_ADDR)
1477            if not internet:
1478                raise signals.TestFailure("Failed to connect to internet on %s" %
1479                                          expected_ssid)
1480    except Empty:
1481        asserts.fail("Failed to start connection process to %s on %s" %
1482                     (network, ad.serial))
1483    except Exception as error:
1484        ad.log.error("Failed to connect to %s with error %s", expected_ssid,
1485                     error)
1486        raise signals.TestFailure("Failed to connect to %s network" % network)
1487
1488    finally:
1489        ad.droid.wifiStopTrackingStateChange()
1490
1491
1492def wifi_connect_by_id(ad, network_id, num_of_tries=3, assert_on_fail=True):
1493    """Connect an Android device to a wifi network using network Id.
1494
1495    Start connection to the wifi network, with the given network Id, wait for
1496    the "connected" event, then verify the connected network is the one requested.
1497
1498    This will directly fail a test if anything goes wrong.
1499
1500    Args:
1501        ad: android_device object to initiate connection on.
1502        network_id: Integer specifying the network id of the network.
1503        num_of_tries: An integer that is the number of times to try before
1504                      delaring failure. Default is 1.
1505        assert_on_fail: If True, error checks in this function will raise test
1506                        failure signals.
1507
1508    Returns:
1509        Returns a value only if assert_on_fail is false.
1510        Returns True if the connection was successful, False otherwise.
1511    """
1512    _assert_on_fail_handler(_wifi_connect_by_id, assert_on_fail, ad,
1513                            network_id, num_of_tries)
1514
1515
1516def _wifi_connect_by_id(ad, network_id, num_of_tries=1):
1517    """Connect an Android device to a wifi network using it's network id.
1518
1519    Start connection to the wifi network, with the given network id, wait for
1520    the "connected" event, then verify the connected network is the one requested.
1521
1522    Args:
1523        ad: android_device object to initiate connection on.
1524        network_id: Integer specifying the network id of the network.
1525        num_of_tries: An integer that is the number of times to try before
1526                      delaring failure. Default is 1.
1527    """
1528    ad.droid.wifiStartTrackingStateChange()
1529    # Clear all previous events.
1530    ad.ed.clear_all_events()
1531    ad.droid.wifiConnectByNetworkId(network_id)
1532    ad.log.info("Starting connection to network with id %d", network_id)
1533    try:
1534        event = ad.ed.pop_event(wifi_constants.CONNECT_BY_NETID_SUCCESS, 60)
1535        connect_result = _wait_for_connect_event(
1536            ad, id=network_id, tries=num_of_tries)
1537        asserts.assert_true(connect_result,
1538                            "Failed to connect to Wi-Fi network using network id")
1539        ad.log.debug("Wi-Fi connection result: %s", connect_result)
1540        actual_id = connect_result['data'][WifiEnums.NETID_KEY]
1541        asserts.assert_equal(actual_id, network_id,
1542                             "Connected to the wrong network on %s."
1543                             "Expected network id = %d, but got %d." %
1544                             (ad.serial, network_id, actual_id))
1545        expected_ssid = connect_result['data'][WifiEnums.SSID_KEY]
1546        ad.log.info("Connected to Wi-Fi network %s with %d network id.",
1547                     expected_ssid, network_id)
1548
1549        internet = validate_connection(ad, DEFAULT_PING_ADDR)
1550        if not internet:
1551            raise signals.TestFailure("Failed to connect to internet on %s" %
1552                                      expected_ssid)
1553    except Empty:
1554        asserts.fail("Failed to connect to network with id %d on %s" %
1555                    (network_id, ad.serial))
1556    except Exception as error:
1557        ad.log.error("Failed to connect to network with id %d with error %s",
1558                      network_id, error)
1559        raise signals.TestFailure("Failed to connect to network with network"
1560                                  " id %d" % network_id)
1561    finally:
1562        ad.droid.wifiStopTrackingStateChange()
1563
1564
1565def wifi_connect_using_network_request(ad, network, network_specifier,
1566                                       num_of_tries=3, assert_on_fail=True):
1567    """Connect an Android device to a wifi network using network request.
1568
1569    Trigger a network request with the provided network specifier,
1570    wait for the "onMatch" event, ensure that the scan results in "onMatch"
1571    event contain the specified network, then simulate the user granting the
1572    request with the specified network selected. Then wait for the "onAvailable"
1573    network callback indicating successful connection to network.
1574
1575    This will directly fail a test if anything goes wrong.
1576
1577    Args:
1578        ad: android_device object to initiate connection on.
1579        network_specifier: A dictionary representing the network specifier to
1580                           use.
1581        network: A dictionary representing the network to connect to. The
1582                 dictionary must have the key "SSID".
1583        num_of_tries: An integer that is the number of times to try before
1584                      delaring failure.
1585        assert_on_fail: If True, error checks in this function will raise test
1586                        failure signals.
1587
1588    Returns:
1589        Returns a value only if assert_on_fail is false.
1590        Returns True if the connection was successful, False otherwise.
1591    """
1592    _assert_on_fail_handler(_wifi_connect_using_network_request, assert_on_fail,
1593                            ad, network, network_specifier, num_of_tries)
1594
1595
1596def _wifi_connect_using_network_request(ad, network, network_specifier,
1597                                        num_of_tries=3):
1598    """Connect an Android device to a wifi network using network request.
1599
1600    Trigger a network request with the provided network specifier,
1601    wait for the "onMatch" event, ensure that the scan results in "onMatch"
1602    event contain the specified network, then simulate the user granting the
1603    request with the specified network selected. Then wait for the "onAvailable"
1604    network callback indicating successful connection to network.
1605
1606    Args:
1607        ad: android_device object to initiate connection on.
1608        network_specifier: A dictionary representing the network specifier to
1609                           use.
1610        network: A dictionary representing the network to connect to. The
1611                 dictionary must have the key "SSID".
1612        num_of_tries: An integer that is the number of times to try before
1613                      delaring failure.
1614    """
1615    ad.droid.wifiRequestNetworkWithSpecifier(network_specifier)
1616    ad.log.info("Sent network request with %s", network_specifier)
1617    # Need a delay here because UI interaction should only start once wifi
1618    # starts processing the request.
1619    time.sleep(wifi_constants.NETWORK_REQUEST_CB_REGISTER_DELAY_SEC)
1620    _wait_for_wifi_connect_after_network_request(ad, network, num_of_tries)
1621
1622
1623def wait_for_wifi_connect_after_network_request(ad, network, num_of_tries=3,
1624                                                assert_on_fail=True):
1625    """
1626    Simulate and verify the connection flow after initiating the network
1627    request.
1628
1629    Wait for the "onMatch" event, ensure that the scan results in "onMatch"
1630    event contain the specified network, then simulate the user granting the
1631    request with the specified network selected. Then wait for the "onAvailable"
1632    network callback indicating successful connection to network.
1633
1634    Args:
1635        ad: android_device object to initiate connection on.
1636        network: A dictionary representing the network to connect to. The
1637                 dictionary must have the key "SSID".
1638        num_of_tries: An integer that is the number of times to try before
1639                      delaring failure.
1640        assert_on_fail: If True, error checks in this function will raise test
1641                        failure signals.
1642
1643    Returns:
1644        Returns a value only if assert_on_fail is false.
1645        Returns True if the connection was successful, False otherwise.
1646    """
1647    _assert_on_fail_handler(_wait_for_wifi_connect_after_network_request,
1648                            assert_on_fail, ad, network, num_of_tries)
1649
1650
1651def _wait_for_wifi_connect_after_network_request(ad, network, num_of_tries=3):
1652    """
1653    Simulate and verify the connection flow after initiating the network
1654    request.
1655
1656    Wait for the "onMatch" event, ensure that the scan results in "onMatch"
1657    event contain the specified network, then simulate the user granting the
1658    request with the specified network selected. Then wait for the "onAvailable"
1659    network callback indicating successful connection to network.
1660
1661    Args:
1662        ad: android_device object to initiate connection on.
1663        network: A dictionary representing the network to connect to. The
1664                 dictionary must have the key "SSID".
1665        num_of_tries: An integer that is the number of times to try before
1666                      delaring failure.
1667    """
1668    asserts.assert_true(WifiEnums.SSID_KEY in network,
1669                        "Key '%s' must be present in network definition." %
1670                        WifiEnums.SSID_KEY)
1671    ad.droid.wifiStartTrackingStateChange()
1672    expected_ssid = network[WifiEnums.SSID_KEY]
1673    ad.droid.wifiRegisterNetworkRequestMatchCallback()
1674    # Wait for the platform to scan and return a list of networks
1675    # matching the request
1676    try:
1677        matched_network = None
1678        for _ in [0,  num_of_tries]:
1679            on_match_event = ad.ed.pop_event(
1680                wifi_constants.WIFI_NETWORK_REQUEST_MATCH_CB_ON_MATCH, 60)
1681            asserts.assert_true(on_match_event,
1682                                "Network request on match not received.")
1683            matched_scan_results = on_match_event["data"]
1684            ad.log.debug("Network request on match results %s",
1685                         matched_scan_results)
1686            matched_network = match_networks(
1687                {WifiEnums.SSID_KEY: network[WifiEnums.SSID_KEY]},
1688                matched_scan_results)
1689            if matched_network:
1690                break;
1691
1692        asserts.assert_true(
1693            matched_network, "Target network %s not found" % network)
1694
1695        ad.droid.wifiSendUserSelectionForNetworkRequestMatch(network)
1696        ad.log.info("Sent user selection for network request %s",
1697                    expected_ssid)
1698
1699        # Wait for the platform to connect to the network.
1700        on_available_event = ad.ed.pop_event(
1701            wifi_constants.WIFI_NETWORK_CB_ON_AVAILABLE, 60)
1702        asserts.assert_true(on_available_event,
1703                            "Network request on available not received.")
1704        connected_network = on_available_event["data"]
1705        ad.log.info("Connected to network %s", connected_network)
1706        asserts.assert_equal(connected_network[WifiEnums.SSID_KEY],
1707                             expected_ssid,
1708                             "Connected to the wrong network."
1709                             "Expected %s, but got %s." %
1710                             (network, connected_network))
1711    except Empty:
1712        asserts.fail("Failed to connect to %s" % expected_ssid)
1713    except Exception as error:
1714        ad.log.error("Failed to connect to %s with error %s",
1715                     (expected_ssid, error))
1716        raise signals.TestFailure("Failed to connect to %s network" % network)
1717    finally:
1718        ad.droid.wifiStopTrackingStateChange()
1719
1720
1721def wifi_passpoint_connect(ad, passpoint_network, num_of_tries=1,
1722                           assert_on_fail=True):
1723    """Connect an Android device to a wifi network.
1724
1725    Initiate connection to a wifi network, wait for the "connected" event, then
1726    confirm the connected ssid is the one requested.
1727
1728    This will directly fail a test if anything goes wrong.
1729
1730    Args:
1731        ad: android_device object to initiate connection on.
1732        passpoint_network: SSID of the Passpoint network to connect to.
1733        num_of_tries: An integer that is the number of times to try before
1734                      delaring failure. Default is 1.
1735        assert_on_fail: If True, error checks in this function will raise test
1736                        failure signals.
1737
1738    Returns:
1739        If assert_on_fail is False, function returns network id, if the connect was
1740        successful, False otherwise. If assert_on_fail is True, no return value.
1741    """
1742    _assert_on_fail_handler(_wifi_passpoint_connect, assert_on_fail, ad,
1743                            passpoint_network, num_of_tries = num_of_tries)
1744
1745
1746def _wifi_passpoint_connect(ad, passpoint_network, num_of_tries=1):
1747    """Connect an Android device to a wifi network.
1748
1749    Initiate connection to a wifi network, wait for the "connected" event, then
1750    confirm the connected ssid is the one requested.
1751
1752    This will directly fail a test if anything goes wrong.
1753
1754    Args:
1755        ad: android_device object to initiate connection on.
1756        passpoint_network: SSID of the Passpoint network to connect to.
1757        num_of_tries: An integer that is the number of times to try before
1758                      delaring failure. Default is 1.
1759    """
1760    ad.droid.wifiStartTrackingStateChange()
1761    expected_ssid = passpoint_network
1762    ad.log.info("Starting connection process to passpoint %s", expected_ssid)
1763
1764    try:
1765        connect_result = _wait_for_connect_event(
1766            ad, expected_ssid, num_of_tries)
1767        asserts.assert_true(connect_result,
1768                            "Failed to connect to WiFi passpoint network %s on"
1769                            " %s" % (expected_ssid, ad.serial))
1770        ad.log.info("Wi-Fi connection result: %s.", connect_result)
1771        actual_ssid = connect_result['data'][WifiEnums.SSID_KEY]
1772        asserts.assert_equal(actual_ssid, expected_ssid,
1773                             "Connected to the wrong network on %s." % ad.serial)
1774        ad.log.info("Connected to Wi-Fi passpoint network %s.", actual_ssid)
1775
1776        internet = validate_connection(ad, DEFAULT_PING_ADDR)
1777        if not internet:
1778            raise signals.TestFailure("Failed to connect to internet on %s" %
1779                                      expected_ssid)
1780    except Exception as error:
1781        ad.log.error("Failed to connect to passpoint network %s with error %s",
1782                      expected_ssid, error)
1783        raise signals.TestFailure("Failed to connect to %s passpoint network" %
1784                                   expected_ssid)
1785
1786    finally:
1787        ad.droid.wifiStopTrackingStateChange()
1788
1789
1790def delete_passpoint(ad, fqdn):
1791    """Delete a required Passpoint configuration."""
1792    try:
1793        ad.droid.removePasspointConfig(fqdn)
1794        return True
1795    except Exception as error:
1796        ad.log.error("Failed to remove passpoint configuration with FQDN=%s "
1797                     "and error=%s" , fqdn, error)
1798        return False
1799
1800
1801def start_wifi_single_scan(ad, scan_setting):
1802    """Starts wifi single shot scan.
1803
1804    Args:
1805        ad: android_device object to initiate connection on.
1806        scan_setting: A dict representing the settings of the scan.
1807
1808    Returns:
1809        If scan was started successfully, event data of success event is returned.
1810    """
1811    idx = ad.droid.wifiScannerStartScan(scan_setting)
1812    event = ad.ed.pop_event("WifiScannerScan%sonSuccess" % idx, SHORT_TIMEOUT)
1813    ad.log.debug("Got event %s", event)
1814    return event['data']
1815
1816
1817def track_connection(ad, network_ssid, check_connection_count):
1818    """Track wifi connection to network changes for given number of counts
1819
1820    Args:
1821        ad: android_device object for forget network.
1822        network_ssid: network ssid to which connection would be tracked
1823        check_connection_count: Integer for maximum number network connection
1824                                check.
1825    Returns:
1826        True if connection to given network happen, else return False.
1827    """
1828    ad.droid.wifiStartTrackingStateChange()
1829    while check_connection_count > 0:
1830        connect_network = ad.ed.pop_event("WifiNetworkConnected", 120)
1831        ad.log.info("Connected to network %s", connect_network)
1832        if (WifiEnums.SSID_KEY in connect_network['data'] and
1833                connect_network['data'][WifiEnums.SSID_KEY] == network_ssid):
1834            return True
1835        check_connection_count -= 1
1836    ad.droid.wifiStopTrackingStateChange()
1837    return False
1838
1839
1840def get_scan_time_and_channels(wifi_chs, scan_setting, stime_channel):
1841    """Calculate the scan time required based on the band or channels in scan
1842    setting
1843
1844    Args:
1845        wifi_chs: Object of channels supported
1846        scan_setting: scan setting used for start scan
1847        stime_channel: scan time per channel
1848
1849    Returns:
1850        scan_time: time required for completing a scan
1851        scan_channels: channel used for scanning
1852    """
1853    scan_time = 0
1854    scan_channels = []
1855    if "band" in scan_setting and "channels" not in scan_setting:
1856        scan_channels = wifi_chs.band_to_freq(scan_setting["band"])
1857    elif "channels" in scan_setting and "band" not in scan_setting:
1858        scan_channels = scan_setting["channels"]
1859    scan_time = len(scan_channels) * stime_channel
1860    for channel in scan_channels:
1861        if channel in WifiEnums.DFS_5G_FREQUENCIES:
1862            scan_time += 132  #passive scan time on DFS
1863    return scan_time, scan_channels
1864
1865
1866def start_wifi_track_bssid(ad, track_setting):
1867    """Start tracking Bssid for the given settings.
1868
1869    Args:
1870      ad: android_device object.
1871      track_setting: Setting for which the bssid tracking should be started
1872
1873    Returns:
1874      If tracking started successfully, event data of success event is returned.
1875    """
1876    idx = ad.droid.wifiScannerStartTrackingBssids(
1877        track_setting["bssidInfos"], track_setting["apLostThreshold"])
1878    event = ad.ed.pop_event("WifiScannerBssid{}onSuccess".format(idx),
1879                            SHORT_TIMEOUT)
1880    return event['data']
1881
1882
1883def convert_pem_key_to_pkcs8(in_file, out_file):
1884    """Converts the key file generated by us to the format required by
1885    Android using openssl.
1886
1887    The input file must have the extension "pem". The output file must
1888    have the extension "der".
1889
1890    Args:
1891        in_file: The original key file.
1892        out_file: The full path to the converted key file, including
1893        filename.
1894    """
1895    asserts.assert_true(in_file.endswith(".pem"), "Input file has to be .pem.")
1896    asserts.assert_true(
1897        out_file.endswith(".der"), "Output file has to be .der.")
1898    cmd = ("openssl pkcs8 -inform PEM -in {} -outform DER -out {} -nocrypt"
1899           " -topk8").format(in_file, out_file)
1900    utils.exe_cmd(cmd)
1901
1902
1903def validate_connection(ad, ping_addr=DEFAULT_PING_ADDR, wait_time=15,
1904                        ping_gateway=True):
1905    """Validate internet connection by pinging the address provided.
1906
1907    Args:
1908        ad: android_device object.
1909        ping_addr: address on internet for pinging.
1910        wait_time: wait for some time before validating connection
1911
1912    Returns:
1913        ping output if successful, NULL otherwise.
1914    """
1915    # wait_time to allow for DHCP to complete.
1916    for i in range(wait_time):
1917        if ad.droid.connectivityNetworkIsConnected():
1918            break
1919        time.sleep(1)
1920    ping = False
1921    try:
1922        ping = ad.droid.httpPing(ping_addr)
1923        ad.log.info("Http ping result: %s.", ping)
1924    except:
1925        pass
1926    if not ping and ping_gateway:
1927        ad.log.info("Http ping failed. Pinging default gateway")
1928        gw = ad.droid.connectivityGetIPv4DefaultGateway()
1929        result = ad.adb.shell("ping -c 6 {}".format(gw))
1930        ad.log.info("Default gateway ping result: %s" % result)
1931        ping = False if "100% packet loss" in result else True
1932    return ping
1933
1934
1935#TODO(angli): This can only verify if an actual value is exactly the same.
1936# Would be nice to be able to verify an actual value is one of serveral.
1937def verify_wifi_connection_info(ad, expected_con):
1938    """Verifies that the information of the currently connected wifi network is
1939    as expected.
1940
1941    Args:
1942        expected_con: A dict representing expected key-value pairs for wifi
1943            connection. e.g. {"SSID": "test_wifi"}
1944    """
1945    current_con = ad.droid.wifiGetConnectionInfo()
1946    case_insensitive = ["BSSID", "supplicant_state"]
1947    ad.log.debug("Current connection: %s", current_con)
1948    for k, expected_v in expected_con.items():
1949        # Do not verify authentication related fields.
1950        if k == "password":
1951            continue
1952        msg = "Field %s does not exist in wifi connection info %s." % (
1953            k, current_con)
1954        if k not in current_con:
1955            raise signals.TestFailure(msg)
1956        actual_v = current_con[k]
1957        if k in case_insensitive:
1958            actual_v = actual_v.lower()
1959            expected_v = expected_v.lower()
1960        msg = "Expected %s to be %s, actual %s is %s." % (k, expected_v, k,
1961                                                          actual_v)
1962        if actual_v != expected_v:
1963            raise signals.TestFailure(msg)
1964
1965
1966def check_autoconnect_to_open_network(ad, conn_timeout=WIFI_CONNECTION_TIMEOUT_DEFAULT):
1967    """Connects to any open WiFI AP
1968     Args:
1969         timeout value in sec to wait for UE to connect to a WiFi AP
1970     Returns:
1971         True if UE connects to WiFi AP (supplicant_state = completed)
1972         False if UE fails to complete connection within WIFI_CONNECTION_TIMEOUT time.
1973    """
1974    if ad.droid.wifiCheckState():
1975        return True
1976    ad.droid.wifiToggleState()
1977    wifi_connection_state = None
1978    timeout = time.time() + conn_timeout
1979    while wifi_connection_state != "completed":
1980        wifi_connection_state = ad.droid.wifiGetConnectionInfo()[
1981            'supplicant_state']
1982        if time.time() > timeout:
1983            ad.log.warning("Failed to connect to WiFi AP")
1984            return False
1985    return True
1986
1987
1988def expand_enterprise_config_by_phase2(config):
1989    """Take an enterprise config and generate a list of configs, each with
1990    a different phase2 auth type.
1991
1992    Args:
1993        config: A dict representing enterprise config.
1994
1995    Returns
1996        A list of enterprise configs.
1997    """
1998    results = []
1999    phase2_types = WifiEnums.EapPhase2
2000    if config[WifiEnums.Enterprise.EAP] == WifiEnums.Eap.PEAP:
2001        # Skip unsupported phase2 types for PEAP.
2002        phase2_types = [WifiEnums.EapPhase2.GTC, WifiEnums.EapPhase2.MSCHAPV2]
2003    for phase2_type in phase2_types:
2004        # Skip a special case for passpoint TTLS.
2005        if (WifiEnums.Enterprise.FQDN in config and
2006                phase2_type == WifiEnums.EapPhase2.GTC):
2007            continue
2008        c = dict(config)
2009        c[WifiEnums.Enterprise.PHASE2] = phase2_type.value
2010        results.append(c)
2011    return results
2012
2013
2014def generate_eap_test_name(config, ad=None):
2015    """ Generates a test case name based on an EAP configuration.
2016
2017    Args:
2018        config: A dict representing an EAP credential.
2019        ad object: Redundant but required as the same param is passed
2020                   to test_func in run_generated_tests
2021
2022    Returns:
2023        A string representing the name of a generated EAP test case.
2024    """
2025    eap = WifiEnums.Eap
2026    eap_phase2 = WifiEnums.EapPhase2
2027    Ent = WifiEnums.Enterprise
2028    name = "test_connect-"
2029    eap_name = ""
2030    for e in eap:
2031        if e.value == config[Ent.EAP]:
2032            eap_name = e.name
2033            break
2034    if "peap0" in config[WifiEnums.SSID_KEY].lower():
2035        eap_name = "PEAP0"
2036    if "peap1" in config[WifiEnums.SSID_KEY].lower():
2037        eap_name = "PEAP1"
2038    name += eap_name
2039    if Ent.PHASE2 in config:
2040        for e in eap_phase2:
2041            if e.value == config[Ent.PHASE2]:
2042                name += "-{}".format(e.name)
2043                break
2044    return name
2045
2046
2047def group_attenuators(attenuators):
2048    """Groups a list of attenuators into attenuator groups for backward
2049    compatibility reasons.
2050
2051    Most legacy Wi-Fi setups have two attenuators each connected to a separate
2052    AP. The new Wi-Fi setup has four attenuators, each connected to one channel
2053    on an AP, so two of them are connected to one AP.
2054
2055    To make the existing scripts work in the new setup, when the script needs
2056    to attenuate one AP, it needs to set attenuation on both attenuators
2057    connected to the same AP.
2058
2059    This function groups attenuators properly so the scripts work in both
2060    legacy and new Wi-Fi setups.
2061
2062    Args:
2063        attenuators: A list of attenuator objects, either two or four in length.
2064
2065    Raises:
2066        signals.TestFailure is raised if the attenuator list does not have two
2067        or four objects.
2068    """
2069    attn0 = attenuator.AttenuatorGroup("AP0")
2070    attn1 = attenuator.AttenuatorGroup("AP1")
2071    # Legacy testbed setup has two attenuation channels.
2072    num_of_attns = len(attenuators)
2073    if num_of_attns == 2:
2074        attn0.add(attenuators[0])
2075        attn1.add(attenuators[1])
2076    elif num_of_attns == 4:
2077        attn0.add(attenuators[0])
2078        attn0.add(attenuators[1])
2079        attn1.add(attenuators[2])
2080        attn1.add(attenuators[3])
2081    else:
2082        asserts.fail(("Either two or four attenuators are required for this "
2083                      "test, but found %s") % num_of_attns)
2084    return [attn0, attn1]
2085
2086
2087def set_attns(attenuator, attn_val_name, roaming_attn=ROAMING_ATTN):
2088    """Sets attenuation values on attenuators used in this test.
2089
2090    Args:
2091        attenuator: The attenuator object.
2092        attn_val_name: Name of the attenuation value pair to use.
2093        roaming_attn: Dictionary specifying the attenuation params.
2094    """
2095    logging.info("Set attenuation values to %s", roaming_attn[attn_val_name])
2096    try:
2097        attenuator[0].set_atten(roaming_attn[attn_val_name][0])
2098        attenuator[1].set_atten(roaming_attn[attn_val_name][1])
2099        attenuator[2].set_atten(roaming_attn[attn_val_name][2])
2100        attenuator[3].set_atten(roaming_attn[attn_val_name][3])
2101    except:
2102        logging.exception("Failed to set attenuation values %s.",
2103                       attn_val_name)
2104        raise
2105
2106def set_attns_steps(attenuators,
2107                    atten_val_name,
2108                    roaming_attn=ROAMING_ATTN,
2109                    steps=10,
2110                    wait_time=12):
2111    """Set attenuation values on attenuators used in this test. It will change
2112    the attenuation values linearly from current value to target value step by
2113    step.
2114
2115    Args:
2116        attenuators: The list of attenuator objects that you want to change
2117                     their attenuation value.
2118        atten_val_name: Name of the attenuation value pair to use.
2119        roaming_attn: Dictionary specifying the attenuation params.
2120        steps: Number of attenuator changes to reach the target value.
2121        wait_time: Sleep time for each change of attenuator.
2122    """
2123    logging.info("Set attenuation values to %s in %d step(s)",
2124            roaming_attn[atten_val_name], steps)
2125    start_atten = [attenuator.get_atten() for attenuator in attenuators]
2126    target_atten = roaming_attn[atten_val_name]
2127    for current_step in range(steps):
2128        progress = (current_step + 1) / steps
2129        for i, attenuator in enumerate(attenuators):
2130            amount_since_start = (target_atten[i] - start_atten[i]) * progress
2131            attenuator.set_atten(round(start_atten[i] + amount_since_start))
2132        time.sleep(wait_time)
2133
2134
2135def trigger_roaming_and_validate(dut,
2136                                 attenuator,
2137                                 attn_val_name,
2138                                 expected_con,
2139                                 roaming_attn=ROAMING_ATTN):
2140    """Sets attenuators to trigger roaming and validate the DUT connected
2141    to the BSSID expected.
2142
2143    Args:
2144        attenuator: The attenuator object.
2145        attn_val_name: Name of the attenuation value pair to use.
2146        expected_con: The network information of the expected network.
2147        roaming_attn: Dictionary specifying the attenaution params.
2148    """
2149    expected_con = {
2150        WifiEnums.SSID_KEY: expected_con[WifiEnums.SSID_KEY],
2151        WifiEnums.BSSID_KEY: expected_con["bssid"],
2152    }
2153    set_attns_steps(attenuator, attn_val_name, roaming_attn)
2154
2155    verify_wifi_connection_info(dut, expected_con)
2156    expected_bssid = expected_con[WifiEnums.BSSID_KEY]
2157    logging.info("Roamed to %s successfully", expected_bssid)
2158    if not validate_connection(dut):
2159        raise signals.TestFailure("Fail to connect to internet on %s" %
2160                                      expected_bssid)
2161
2162def create_softap_config():
2163    """Create a softap config with random ssid and password."""
2164    ap_ssid = "softap_" + utils.rand_ascii_str(8)
2165    ap_password = utils.rand_ascii_str(8)
2166    logging.info("softap setup: %s %s", ap_ssid, ap_password)
2167    config = {
2168        WifiEnums.SSID_KEY: ap_ssid,
2169        WifiEnums.PWD_KEY: ap_password,
2170    }
2171    return config
2172
2173def start_softap_and_verify(ad, band):
2174    """Bring-up softap and verify AP mode and in scan results.
2175
2176    Args:
2177        band: The band to use for softAP.
2178
2179    Returns: dict, the softAP config.
2180
2181    """
2182    # Register before start the test.
2183    callbackId = ad.dut.droid.registerSoftApCallback()
2184    # Check softap info value is default
2185    frequency, bandwdith = get_current_softap_info(ad.dut, callbackId, True)
2186    asserts.assert_true(frequency == 0, "Softap frequency is not reset")
2187    asserts.assert_true(bandwdith == 0, "Softap bandwdith is not reset")
2188
2189    config = create_softap_config()
2190    start_wifi_tethering(ad.dut,
2191                         config[WifiEnums.SSID_KEY],
2192                         config[WifiEnums.PWD_KEY], band=band)
2193    asserts.assert_true(ad.dut.droid.wifiIsApEnabled(),
2194                         "SoftAp is not reported as running")
2195    start_wifi_connection_scan_and_ensure_network_found(ad.dut_client,
2196        config[WifiEnums.SSID_KEY])
2197
2198    # Check softap info can get from callback succeed and assert value should be
2199    # valid.
2200    frequency, bandwdith = get_current_softap_info(ad.dut, callbackId, True)
2201    asserts.assert_true(frequency > 0, "Softap frequency is not valid")
2202    asserts.assert_true(bandwdith > 0, "Softap bandwdith is not valid")
2203    # Unregister callback
2204    ad.dut.droid.unregisterSoftApCallback(callbackId)
2205
2206    return config
2207
2208def wait_for_expected_number_of_softap_clients(ad, callbackId,
2209        expected_num_of_softap_clients):
2210    """Wait for the number of softap clients to be updated as expected.
2211    Args:
2212        callbackId: Id of the callback associated with registering.
2213        expected_num_of_softap_clients: expected number of softap clients.
2214    """
2215    eventStr = wifi_constants.SOFTAP_CALLBACK_EVENT + str(
2216            callbackId) + wifi_constants.SOFTAP_NUMBER_CLIENTS_CHANGED
2217    clientData = ad.ed.pop_event(eventStr, SHORT_TIMEOUT)['data']
2218    clientCount = clientData[wifi_constants.SOFTAP_NUMBER_CLIENTS_CALLBACK_KEY]
2219    clientMacAddresses = clientData[wifi_constants.SOFTAP_CLIENTS_MACS_CALLBACK_KEY]
2220    asserts.assert_equal(clientCount, expected_num_of_softap_clients,
2221            "The number of softap clients doesn't match the expected number")
2222    asserts.assert_equal(len(clientMacAddresses), expected_num_of_softap_clients,
2223                         "The number of mac addresses doesn't match the expected number")
2224    for macAddress in clientMacAddresses:
2225        asserts.assert_true(checkMacAddress(macAddress), "An invalid mac address was returned")
2226
2227def checkMacAddress(input):
2228    """Validate whether a string is a valid mac address or not.
2229
2230    Args:
2231        input: The string to validate.
2232
2233    Returns: True/False, returns true for a valid mac address and false otherwise.
2234    """
2235    macValidationRegex = "[0-9a-f]{2}([-:]?)[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$"
2236    if re.match(macValidationRegex, input.lower()):
2237        return True
2238    return False
2239
2240def wait_for_expected_softap_state(ad, callbackId, expected_softap_state):
2241    """Wait for the expected softap state change.
2242    Args:
2243        callbackId: Id of the callback associated with registering.
2244        expected_softap_state: The expected softap state.
2245    """
2246    eventStr = wifi_constants.SOFTAP_CALLBACK_EVENT + str(
2247            callbackId) + wifi_constants.SOFTAP_STATE_CHANGED
2248    asserts.assert_equal(ad.ed.pop_event(eventStr,
2249            SHORT_TIMEOUT)['data'][wifi_constants.
2250            SOFTAP_STATE_CHANGE_CALLBACK_KEY],
2251            expected_softap_state,
2252            "Softap state doesn't match with expected state")
2253
2254def get_current_number_of_softap_clients(ad, callbackId):
2255    """pop up all of softap client updated event from queue.
2256    Args:
2257        callbackId: Id of the callback associated with registering.
2258
2259    Returns:
2260        If exist aleast callback, returns last updated number_of_softap_clients.
2261        Returns None when no any match callback event in queue.
2262    """
2263    eventStr = wifi_constants.SOFTAP_CALLBACK_EVENT + str(
2264            callbackId) + wifi_constants.SOFTAP_NUMBER_CLIENTS_CHANGED
2265    events = ad.ed.pop_all(eventStr)
2266    for event in events:
2267        num_of_clients = event['data'][wifi_constants.
2268                SOFTAP_NUMBER_CLIENTS_CALLBACK_KEY]
2269    if len(events) == 0:
2270        return None
2271    return num_of_clients
2272
2273def get_current_softap_info(ad, callbackId, least_one):
2274    """pop up all of softap info changed event from queue.
2275    Args:
2276        callbackId: Id of the callback associated with registering.
2277        least_one: Wait for the info callback event before pop all.
2278    Returns:
2279        Returns last updated information of softap.
2280    """
2281    eventStr = wifi_constants.SOFTAP_CALLBACK_EVENT + str(
2282            callbackId) + wifi_constants.SOFTAP_INFO_CHANGED
2283    ad.log.info("softap info dump from eventStr %s",
2284                eventStr)
2285    frequency = 0
2286    bandwidth = 0
2287    if (least_one):
2288        event = ad.ed.pop_event(eventStr, SHORT_TIMEOUT)
2289        frequency = event['data'][wifi_constants.
2290                SOFTAP_INFO_FREQUENCY_CALLBACK_KEY]
2291        bandwidth = event['data'][wifi_constants.
2292                SOFTAP_INFO_BANDWIDTH_CALLBACK_KEY]
2293        ad.log.info("softap info updated, frequency is %s, bandwidth is %s",
2294            frequency, bandwidth)
2295
2296    events = ad.ed.pop_all(eventStr)
2297    for event in events:
2298        frequency = event['data'][wifi_constants.
2299                SOFTAP_INFO_FREQUENCY_CALLBACK_KEY]
2300        bandwidth = event['data'][wifi_constants.
2301                SOFTAP_INFO_BANDWIDTH_CALLBACK_KEY]
2302    ad.log.info("softap info, frequency is %s, bandwidth is %s",
2303            frequency, bandwidth)
2304    return frequency, bandwidth
2305
2306
2307
2308def get_ssrdumps(ad, test_name=""):
2309    """Pulls dumps in the ssrdump dir
2310    Args:
2311        ad: android device object.
2312        test_name: test case name
2313    """
2314    logs = ad.get_file_names("/data/vendor/ssrdump/")
2315    if logs:
2316        ad.log.info("Pulling ssrdumps %s", logs)
2317        log_path = os.path.join(ad.log_path, test_name,
2318                                "SSRDUMP_%s" % ad.serial)
2319        os.makedirs(log_path, exist_ok=True)
2320        ad.pull_files(logs, log_path)
2321    ad.adb.shell("find /data/vendor/ssrdump/ -type f -delete")
2322
2323def start_pcap(pcap, wifi_band, test_name):
2324    """Start packet capture in monitor mode.
2325
2326    Args:
2327        pcap: packet capture object
2328        wifi_band: '2g' or '5g' or 'dual'
2329        test_name: test name to be used for pcap file name
2330
2331    Returns:
2332        Dictionary with wifi band as key and the tuple
2333        (pcap Process object, log directory) as the value
2334    """
2335    log_dir = os.path.join(
2336        context.get_current_context().get_full_output_path(), 'PacketCapture')
2337    os.makedirs(log_dir, exist_ok=True)
2338    if wifi_band == 'dual':
2339        bands = [BAND_2G, BAND_5G]
2340    else:
2341        bands = [wifi_band]
2342    procs = {}
2343    for band in bands:
2344        proc = pcap.start_packet_capture(band, log_dir, test_name)
2345        procs[band] = (proc, os.path.join(log_dir, test_name))
2346    return procs
2347
2348
2349def stop_pcap(pcap, procs, test_status=None):
2350    """Stop packet capture in monitor mode.
2351
2352    Since, the pcap logs in monitor mode can be very large, we will
2353    delete them if they are not required. 'test_status' if True, will delete
2354    the pcap files. If False, we will keep them.
2355
2356    Args:
2357        pcap: packet capture object
2358        procs: dictionary returned by start_pcap
2359        test_status: status of the test case
2360    """
2361    for proc, fname in procs.values():
2362        pcap.stop_packet_capture(proc)
2363
2364    if test_status:
2365        shutil.rmtree(os.path.dirname(fname))
2366
2367def verify_mac_not_found_in_pcap(ad, mac, packets):
2368    """Verify that a mac address is not found in the captured packets.
2369
2370    Args:
2371        ad: android device object
2372        mac: string representation of the mac address
2373        packets: packets obtained by rdpcap(pcap_fname)
2374    """
2375    for pkt in packets:
2376        logging.debug("Packet Summary = %s", pkt.summary())
2377        if mac in pkt.summary():
2378            asserts.fail("Device %s caught Factory MAC: %s in packet sniffer."
2379                         "Packet = %s" % (ad.serial, mac, pkt.show()))
2380
2381def verify_mac_is_found_in_pcap(ad, mac, packets):
2382    """Verify that a mac address is found in the captured packets.
2383
2384    Args:
2385        ad: android device object
2386        mac: string representation of the mac address
2387        packets: packets obtained by rdpcap(pcap_fname)
2388    """
2389    for pkt in packets:
2390        if mac in pkt.summary():
2391            return
2392    asserts.fail("Did not find MAC = %s in packet sniffer."
2393                 "for device %s" % (mac, ad.serial))
2394
2395def start_cnss_diags(ads):
2396    for ad in ads:
2397        start_cnss_diag(ad)
2398
2399
2400def start_cnss_diag(ad):
2401    """Start cnss_diag to record extra wifi logs
2402
2403    Args:
2404        ad: android device object.
2405    """
2406    if ad.model in wifi_constants.DEVICES_USING_LEGACY_PROP:
2407        prop = wifi_constants.LEGACY_CNSS_DIAG_PROP
2408    else:
2409        prop = wifi_constants.CNSS_DIAG_PROP
2410    if ad.adb.getprop(prop) != 'true':
2411        ad.adb.shell("find /data/vendor/wifi/cnss_diag/wlan_logs/ -type f -delete")
2412        ad.adb.shell("setprop %s true" % prop, ignore_status=True)
2413
2414
2415def stop_cnss_diags(ads):
2416    for ad in ads:
2417        stop_cnss_diag(ad)
2418
2419
2420def stop_cnss_diag(ad):
2421    """Stops cnss_diag
2422
2423    Args:
2424        ad: android device object.
2425    """
2426    if ad.model in wifi_constants.DEVICES_USING_LEGACY_PROP:
2427        prop = wifi_constants.LEGACY_CNSS_DIAG_PROP
2428    else:
2429        prop = wifi_constants.CNSS_DIAG_PROP
2430    ad.adb.shell("setprop %s false" % prop, ignore_status=True)
2431
2432
2433def get_cnss_diag_log(ad, test_name=""):
2434    """Pulls the cnss_diag logs in the wlan_logs dir
2435    Args:
2436        ad: android device object.
2437        test_name: test case name
2438    """
2439    logs = ad.get_file_names("/data/vendor/wifi/cnss_diag/wlan_logs/")
2440    if logs:
2441        ad.log.info("Pulling cnss_diag logs %s", logs)
2442        log_path = os.path.join(ad.device_log_path, "CNSS_DIAG_%s" % ad.serial)
2443        os.makedirs(log_path, exist_ok=True)
2444        ad.pull_files(logs, log_path)
2445
2446
2447LinkProbeResult = namedtuple('LinkProbeResult', (
2448    'is_success', 'stdout', 'elapsed_time', 'failure_reason'))
2449
2450
2451def send_link_probe(ad):
2452    """Sends a link probe to the currently connected AP, and returns whether the
2453    probe succeeded or not.
2454
2455    Args:
2456         ad: android device object
2457    Returns:
2458        LinkProbeResult namedtuple
2459    """
2460    stdout = ad.adb.shell('cmd wifi send-link-probe')
2461    asserts.assert_false('Error' in stdout or 'Exception' in stdout,
2462                         'Exception while sending link probe: ' + stdout)
2463
2464    is_success = False
2465    elapsed_time = None
2466    failure_reason = None
2467    if 'succeeded' in stdout:
2468        is_success = True
2469        elapsed_time = next(
2470            (int(token) for token in stdout.split() if token.isdigit()), None)
2471    elif 'failed with reason' in stdout:
2472        failure_reason = next(
2473            (int(token) for token in stdout.split() if token.isdigit()), None)
2474    else:
2475        asserts.fail('Unexpected link probe result: ' + stdout)
2476
2477    return LinkProbeResult(
2478        is_success=is_success, stdout=stdout,
2479        elapsed_time=elapsed_time, failure_reason=failure_reason)
2480
2481
2482def send_link_probes(ad, num_probes, delay_sec):
2483    """Sends a sequence of link probes to the currently connected AP, and
2484    returns whether the probes succeeded or not.
2485
2486    Args:
2487         ad: android device object
2488         num_probes: number of probes to perform
2489         delay_sec: delay time between probes, in seconds
2490    Returns:
2491        List[LinkProbeResult] one LinkProbeResults for each probe
2492    """
2493    logging.info('Sending link probes')
2494    results = []
2495    for _ in range(num_probes):
2496        # send_link_probe() will also fail the test if it sees an exception
2497        # in the stdout of the adb shell command
2498        result = send_link_probe(ad)
2499        logging.info('link probe results: ' + str(result))
2500        results.append(result)
2501        time.sleep(delay_sec)
2502
2503    return results
2504
2505
2506def ap_setup(test, index, ap, network, bandwidth=80, channel=6):
2507        """Set up the AP with provided network info.
2508
2509        Args:
2510            test: the calling test class object.
2511            index: int, index of the AP.
2512            ap: access_point object of the AP.
2513            network: dict with information of the network, including ssid,
2514                     password and bssid.
2515            bandwidth: the operation bandwidth for the AP, default 80MHz.
2516            channel: the channel number for the AP.
2517        Returns:
2518            brconfigs: the bridge interface configs
2519        """
2520        bss_settings = []
2521        ssid = network[WifiEnums.SSID_KEY]
2522        test.access_points[index].close()
2523        time.sleep(5)
2524
2525        # Configure AP as required.
2526        if "password" in network.keys():
2527            password = network["password"]
2528            security = hostapd_security.Security(
2529                security_mode="wpa", password=password)
2530        else:
2531            security = hostapd_security.Security(security_mode=None, password=None)
2532        config = hostapd_ap_preset.create_ap_preset(
2533                                                    channel=channel,
2534                                                    ssid=ssid,
2535                                                    security=security,
2536                                                    bss_settings=bss_settings,
2537                                                    vht_bandwidth=bandwidth,
2538                                                    profile_name='whirlwind',
2539                                                    iface_wlan_2g=ap.wlan_2g,
2540                                                    iface_wlan_5g=ap.wlan_5g)
2541        ap.start_ap(config)
2542        logging.info("AP started on channel {} with SSID {}".format(channel, ssid))
2543
2544
2545def turn_ap_off(test, AP):
2546    """Bring down hostapd on the Access Point.
2547    Args:
2548        test: The test class object.
2549        AP: int, indicating which AP to turn OFF.
2550    """
2551    hostapd_2g = test.access_points[AP-1]._aps['wlan0'].hostapd
2552    if hostapd_2g.is_alive():
2553        hostapd_2g.stop()
2554        logging.debug('Turned WLAN0 AP%d off' % AP)
2555    hostapd_5g = test.access_points[AP-1]._aps['wlan1'].hostapd
2556    if hostapd_5g.is_alive():
2557        hostapd_5g.stop()
2558        logging.debug('Turned WLAN1 AP%d off' % AP)
2559
2560
2561def turn_ap_on(test, AP):
2562    """Bring up hostapd on the Access Point.
2563    Args:
2564        test: The test class object.
2565        AP: int, indicating which AP to turn ON.
2566    """
2567    hostapd_2g = test.access_points[AP-1]._aps['wlan0'].hostapd
2568    if not hostapd_2g.is_alive():
2569        hostapd_2g.start(hostapd_2g.config)
2570        logging.debug('Turned WLAN0 AP%d on' % AP)
2571    hostapd_5g = test.access_points[AP-1]._aps['wlan1'].hostapd
2572    if not hostapd_5g.is_alive():
2573        hostapd_5g.start(hostapd_5g.config)
2574        logging.debug('Turned WLAN1 AP%d on' % AP)
2575
2576
2577def turn_location_off_and_scan_toggle_off(ad):
2578    """Turns off wifi location scans."""
2579    utils.set_location_service(ad, False)
2580    ad.droid.wifiScannerToggleAlwaysAvailable(False)
2581    msg = "Failed to turn off location service's scan."
2582    asserts.assert_true(not ad.droid.wifiScannerIsAlwaysAvailable(), msg)
2583