1#!/usr/bin/env python3.4
2#
3#   Copyright 2018 - The Android Open Source Project
4#
5#   Licensed under the Apache License, Version 2.0 (the "License");
6#   you may not use this file except in compliance with the License.
7#   You may obtain a copy of the License at
8#
9#       http://www.apache.org/licenses/LICENSE-2.0
10#
11#   Unless required by applicable law or agreed to in writing, software
12#   distributed under the License is distributed on an "AS IS" BASIS,
13#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14#   See the License for the specific language governing permissions and
15#   limitations under the License.
16
17import time
18import queue
19
20from acts import asserts
21from acts.controllers.android_device import SL4A_APK_NAME
22from acts.test_decorators import test_tracker_info
23from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
24import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
25import acts.utils
26
27WifiEnums = wutils.WifiEnums
28SSID = WifiEnums.SSID_KEY
29CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT = 5
30SCANS_REQUIRED_TO_FIND_SSID = 5
31LAST_DISCONNECT_TIMEOUT_MILLIS = 5000
32LAST_DISCONNECT_TIMEOUT_SEC = LAST_DISCONNECT_TIMEOUT_MILLIS / 1000
33PRESCAN_DELAY_SEC = 5
34WIFI_TOGGLE_DELAY_SEC = 3
35DISCONNECT_TIMEOUT_SEC = 20
36
37
38class WifiWakeTest(WifiBaseTest):
39    """
40    Tests Wifi Wake.
41
42    Test Bed Requirements:
43    * One Android Device
44    * Two APs that can be turned on and off
45    """
46    def __init__(self, configs):
47        super().__init__(configs)
48        self.enable_packet_log = True
49
50    def setup_class(self):
51        super().setup_class()
52
53        self.dut = self.android_devices[0]
54        wutils.wifi_test_device_init(self.dut)
55        # turn location back on
56        acts.utils.set_location_service(self.dut, True)
57        self.dut.droid.wifiScannerToggleAlwaysAvailable(True)
58
59        self.unpack_userparams(req_param_names=[],
60                               opt_param_names=["reference_networks",
61                                                "google_pixel_watch_models"])
62
63        if "AccessPoint" in self.user_params:
64            self.legacy_configure_ap_and_start(mirror_ap=False, ap_count=2)
65        elif "OpenWrtAP" in self.user_params:
66            self.configure_openwrt_ap_and_start(wpa_network=True,
67                                                ap_count=2)
68
69        # use 2G since Wifi Wake does not work if an AP is on a 5G DFS channel
70        self.ap_a = self.reference_networks[0]["2g"]
71        self.ap_b = self.reference_networks[1]["2g"]
72
73        self.ap_a_atten = self.attenuators[0]
74        self.ap_b_atten = self.attenuators[2]
75        if "OpenWrtAP" in self.user_params:
76            self.ap_b_atten = self.attenuators[1]
77
78    # TODO(b/119040540): this method of disabling/re-enabling Wifi on APs is
79    # hacky, switch to using public methods when they are implemented
80    def ap_a_off(self):
81        if "OpenWrtAP" in self.user_params:
82            self.access_points[0].stop_ap()
83            self.log.info('Turned AP A off')
84            return
85        ap_a_hostapd = self.access_points[0]._aps['wlan0'].hostapd
86        if ap_a_hostapd.is_alive():
87            ap_a_hostapd.stop()
88            self.log.info('Turned AP A off')
89
90    def ap_a_on(self):
91        if "OpenWrtAP" in self.user_params:
92            self.access_points[0].start_ap()
93            self.log.info('Turned AP A on')
94            return
95        ap_a_hostapd = self.access_points[0]._aps['wlan0'].hostapd
96        if not ap_a_hostapd.is_alive():
97            ap_a_hostapd.start(ap_a_hostapd.config)
98            self.log.info('Turned AP A on')
99
100    def ap_b_off(self):
101        if "OpenWrtAP" in self.user_params:
102            self.access_points[1].stop_ap()
103            self.log.info('Turned AP B off')
104            return
105        ap_b_hostapd = self.access_points[1]._aps['wlan0'].hostapd
106        if ap_b_hostapd.is_alive():
107            ap_b_hostapd.stop()
108            self.log.info('Turned AP B off')
109
110    def ap_b_on(self):
111        if "OpenWrtAP" in self.user_params:
112            self.access_points[1].start_ap()
113            self.log.info('Turned AP B on')
114            return
115        ap_b_hostapd = self.access_points[1]._aps['wlan0'].hostapd
116        if not ap_b_hostapd.is_alive():
117            ap_b_hostapd.start(ap_b_hostapd.config)
118            self.log.info('Turned AP B on')
119
120    def setup_test(self):
121        super().setup_test()
122        self.dut.droid.wakeLockAcquireBright()
123        self.dut.droid.wakeUpNow()
124        self.ap_a_on()
125        self.ap_b_on()
126        self.ap_a_atten.set_atten(0)
127        self.ap_b_atten.set_atten(0)
128        wutils.reset_wifi(self.dut)
129        wutils.wifi_toggle_state(self.dut, new_state=True)
130        # clear events from event dispatcher
131        self.dut.droid.wifiStartTrackingStateChange()
132        self.dut.droid.wifiStopTrackingStateChange()
133        self.dut.ed.clear_all_events()
134
135    def teardown_test(self):
136        super().teardown_test()
137        self.dut.droid.wakeLockRelease()
138        self.dut.droid.goToSleepNow()
139
140    def find_ssid_in_scan_results(self, scan_results_batches, ssid):
141        scan_results_batch = scan_results_batches[0]
142        scan_results = scan_results_batch["ScanResults"]
143        for scan_result in scan_results:
144            if ssid == scan_result["SSID"]:
145               return True
146        return False
147
148    def do_location_scan(self, num_times=1, ssid_to_find=None):
149        scan_settings = {
150            "band": wutils.WifiEnums.WIFI_BAND_BOTH,
151            "periodInMs": 0,
152            "reportEvents": wutils.WifiEnums.REPORT_EVENT_AFTER_EACH_SCAN
153        }
154
155        wifi_chs = wutils.WifiChannelUS(self.dut.model)
156        stime_channel = 47  # dwell time plus 2ms
157        leeway = 10
158
159        for i in range(num_times):
160            self.log.info("Scan count: {}".format(i))
161            data = wutils.start_wifi_single_scan(self.dut, scan_settings)
162            idx = data["Index"]
163            scan_rt = data["ScanElapsedRealtime"]
164            self.log.debug(
165                "Wifi single shot scan started index: %s at real time: %s", idx,
166                scan_rt)
167            # generating event wait time from scan setting plus leeway
168            scan_time, scan_channels = wutils.get_scan_time_and_channels(
169                wifi_chs, scan_settings, stime_channel)
170            wait_time = int(scan_time / 1000) + leeway
171            # track number of result received
172            result_received = 0
173            try:
174                for _ in range(1, 3):
175                    event_name = "{}{}onResults".format("WifiScannerScan", idx)
176                    self.log.debug("Waiting for event: %s for time %s",
177                                   event_name, wait_time)
178                    event = self.dut.ed.pop_event(event_name, wait_time)
179                    self.log.debug("Event received: %s", event)
180                    result_received += 1
181                    scan_results_batches = event["data"]["Results"]
182                    if ssid_to_find and self.find_ssid_in_scan_results(
183                        scan_results_batches, ssid_to_find):
184                        return
185            except queue.Empty as error:
186                asserts.assert_true(
187                    result_received >= 1,
188                    "Event did not triggered for single shot {}".format(error))
189            finally:
190                self.dut.droid.wifiScannerStopScan(idx)
191                # For single shot number of result received and length of result
192                # should be one
193                asserts.assert_true(
194                    result_received == 1,
195                    "Test fail because received result {}".format(
196                        result_received))
197
198    @test_tracker_info(uuid="372b9b74-4241-46ce-8f18-e6a97d3a3452")
199    def test_no_reconnect_manual_disable_wifi(self):
200        """
201        Tests that Wifi Wake does not reconnect to a network if the user turned
202        off Wifi while connected to that network and the user has not moved
203        (i.e. moved out of range of the AP then came back).
204        """
205        if "google_pixel_watch_models" in self.user_params:
206            if self.dut.model in self.user_params["google_pixel_watch_models"]:
207                wutils.disable_wear_wifimediator(self.dut, True)
208
209        wutils.wifi_connect(self.dut, self.ap_a, num_of_tries=5)
210        wutils.wifi_toggle_state(self.dut, new_state=False)
211        time.sleep(PRESCAN_DELAY_SEC)
212        self.do_location_scan(
213            2 * CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
214        asserts.assert_false(
215            self.dut.droid.wifiCheckState(),
216            "Expect Wifi Wake to not enable Wifi, but Wifi was enabled.")
217
218        if "google_pixel_watch_models" in self.user_params:
219            if self.dut.model in self.user_params["google_pixel_watch_models"]:
220                wutils.disable_wear_wifimediator(self.dut, False)
221
222    @test_tracker_info(uuid="ec7a54a5-f293-43f5-a1dd-d41679aa1825")
223    def test_reconnect_wifi_saved_network(self):
224        """Tests that Wifi Wake re-enables Wifi for a saved network."""
225        wutils.wifi_connect(self.dut, self.ap_a, num_of_tries=5)
226        wutils.wifi_connect(self.dut, self.ap_b, num_of_tries=5)
227        self.dut.ed.clear_all_events()
228        self.ap_a_off()
229        self.ap_b_off()
230        wutils.wait_for_disconnect(self.dut, DISCONNECT_TIMEOUT_SEC)
231        self.log.info("Wifi Disconnected")
232        self.do_location_scan(2)
233        time.sleep(LAST_DISCONNECT_TIMEOUT_SEC * 1.2)
234        wutils.wifi_toggle_state(self.dut, new_state=False)
235        time.sleep(PRESCAN_DELAY_SEC)
236        self.do_location_scan(2 * CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
237
238        self.ap_a_on()
239        self.do_location_scan(
240            SCANS_REQUIRED_TO_FIND_SSID, self.ap_a[wutils.WifiEnums.SSID_KEY])
241        time.sleep(WIFI_TOGGLE_DELAY_SEC)
242        asserts.assert_true(
243            self.dut.droid.wifiCheckState(),
244            "Expect Wifi Wake to enable Wifi, but Wifi is disabled.")
245
246    @test_tracker_info(uuid="3cecd1c5-54bc-44a2-86f7-ad84625bf094")
247    def test_reconnect_wifi_network_suggestion(self):
248        """Tests that Wifi Wake re-enables Wifi for app provided suggestion."""
249        self.dut.log.info("Adding network suggestions")
250        asserts.assert_true(
251            self.dut.droid.wifiAddNetworkSuggestions([self.ap_a]),
252            "Failed to add suggestions")
253        asserts.assert_true(
254            self.dut.droid.wifiAddNetworkSuggestions([self.ap_b]),
255            "Failed to add suggestions")
256        # Enable suggestions by the app.
257        self.dut.log.debug("Enabling suggestions from test")
258        self.dut.adb.shell("cmd wifi network-suggestions-set-user-approved"
259                           + " " + SL4A_APK_NAME + " yes")
260        # Ensure network is seen in scan results & auto-connected to.
261        self.do_location_scan(2)
262        wutils.wait_for_connect(self.dut)
263        current_network = self.dut.droid.wifiGetConnectionInfo()
264        self.dut.ed.clear_all_events()
265        if current_network[SSID] == self.ap_a[SSID]:
266            # connected to AP A, so turn AP B off first to prevent the
267            # device from immediately reconnecting to AP B
268            self.ap_b_off()
269            self.ap_a_off()
270        else:
271            self.ap_a_off()
272            self.ap_b_off()
273
274        wutils.wait_for_disconnect(self.dut, DISCONNECT_TIMEOUT_SEC)
275        self.log.info("Wifi Disconnected")
276        self.do_location_scan(2)
277        time.sleep(LAST_DISCONNECT_TIMEOUT_SEC * 1.2)
278        wutils.wifi_toggle_state(self.dut, new_state=False)
279        time.sleep(PRESCAN_DELAY_SEC)
280        self.do_location_scan(2 * CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
281
282        self.ap_a_on()
283        self.do_location_scan(
284            SCANS_REQUIRED_TO_FIND_SSID, self.ap_a[wutils.WifiEnums.SSID_KEY])
285        time.sleep(WIFI_TOGGLE_DELAY_SEC)
286        asserts.assert_true(
287            self.dut.droid.wifiCheckState(),
288            "Expect Wifi Wake to enable Wifi, but Wifi is disabled.")
289
290    @test_tracker_info(uuid="6c77ca9b-ff34-4bc7-895f-cc7340e0e645")
291    def test_reconnect_wifi_move_back_in_range(self):
292        """
293        Tests that Wifi Wake re-enables Wifi if the device moves out of range of
294        the AP then came back.
295        """
296        wutils.wifi_connect(self.dut, self.ap_a, num_of_tries=5)
297        wutils.wifi_toggle_state(self.dut, new_state=False)
298        time.sleep(PRESCAN_DELAY_SEC)
299        # init Wakeup Lock with AP A
300        self.do_location_scan(CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
301        self.ap_a_off()
302        # evict AP A from Wakeup Lock
303        self.do_location_scan(CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
304        self.ap_a_on()
305        self.do_location_scan(
306            SCANS_REQUIRED_TO_FIND_SSID, self.ap_a[wutils.WifiEnums.SSID_KEY])
307        time.sleep(WIFI_TOGGLE_DELAY_SEC)
308        asserts.assert_true(
309            self.dut.droid.wifiCheckState(),
310            "Expect Wifi Wake to enable Wifi, but Wifi is disabled.")
311
312    @test_tracker_info(uuid="08e8284a-a523-48f3-b9ea-9c6bf27d711e")
313    def test_no_reconnect_to_flaky_ap(self):
314        """
315        Tests that Wifi Wake does not reconnect to flaky networks.
316        If a network sporadically connects and disconnects, and the user turns
317        off Wifi even during the disconnected phase, Wifi Wake should not
318        re-enable Wifi for that network.
319        """
320        wutils.wifi_connect(self.dut, self.ap_a, num_of_tries=5)
321        self.ap_a_off()
322        time.sleep(LAST_DISCONNECT_TIMEOUT_SEC * 0.4)
323        wutils.wifi_toggle_state(self.dut, new_state=False)
324        time.sleep(PRESCAN_DELAY_SEC)
325        self.do_location_scan(CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
326        self.ap_a_on()
327        self.do_location_scan(
328            2 * CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
329        asserts.assert_false(
330            self.dut.droid.wifiCheckState(),
331            "Expect Wifi Wake to not enable Wifi, but Wifi was enabled.")
332
333    @test_tracker_info(uuid="b990a8f7-e3a0-4774-89cf-2067ccd64903")
334    def test_reconnect_wifi_disabled_after_disconnecting(self):
335        """
336        Tests that Wifi Wake reconnects to a network if Wifi was disabled long
337        after disconnecting from a network.
338        """
339        wutils.wifi_connect(self.dut, self.ap_a, num_of_tries=5)
340        self.dut.ed.clear_all_events()
341        self.ap_a_off()
342        wutils.wait_for_disconnect(self.dut, DISCONNECT_TIMEOUT_SEC)
343        self.log.info("Wifi Disconnected")
344        self.do_location_scan(2)
345        time.sleep(LAST_DISCONNECT_TIMEOUT_SEC * 1.2)
346        wutils.wifi_toggle_state(self.dut, new_state=False)
347        time.sleep(PRESCAN_DELAY_SEC)
348        self.do_location_scan(2 * CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
349        self.ap_a_on()
350        self.do_location_scan(
351            SCANS_REQUIRED_TO_FIND_SSID, self.ap_a[wutils.WifiEnums.SSID_KEY])
352        time.sleep(WIFI_TOGGLE_DELAY_SEC)
353        asserts.assert_true(
354            self.dut.droid.wifiCheckState(),
355            "Expect Wifi Wake to enable Wifi, but Wifi is disabled.")
356
357    @test_tracker_info(uuid="bb217794-d3ee-4fb9-87ff-7a594d0223b0")
358    def test_no_reconnect_if_exists_ap_in_wakeup_lock(self):
359        """
360        2 APs in Wakeup Lock, user moves out of range of one AP but stays in
361        range of the other, should not reconnect when user moves back in range
362        of both.
363        """
364        wutils.wifi_connect(self.dut, self.ap_a, num_of_tries=5)
365        wutils.wifi_connect(self.dut, self.ap_b, num_of_tries=5)
366        wutils.wifi_toggle_state(self.dut, new_state=False)
367        time.sleep(PRESCAN_DELAY_SEC)
368        self.do_location_scan(CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
369        self.ap_b_off()
370        self.do_location_scan(CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
371        self.ap_b_on()
372        self.do_location_scan(CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
373        asserts.assert_false(
374            self.dut.droid.wifiCheckState(),
375            "Expect Wifi Wake to not enable Wifi, but Wifi was enabled.")
376
377    @test_tracker_info(uuid="567a0663-4ce0-488d-8fe2-db79a3ebf068")
378    def test_reconnect_if_both_ap_evicted_from_wakeup_lock(self):
379        """
380        2 APs in Wakeup Lock, user moves out of range of both APs, should
381        reconnect when user moves back in range of either AP.
382        """
383        wutils.wifi_connect(self.dut, self.ap_a, num_of_tries=5)
384        wutils.wifi_connect(self.dut, self.ap_b, num_of_tries=5)
385        wutils.wifi_toggle_state(self.dut, new_state=False)
386        time.sleep(PRESCAN_DELAY_SEC)
387        self.do_location_scan(CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
388        self.ap_a_off()
389        self.ap_b_off()
390        self.do_location_scan(CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
391        self.ap_a_on()
392        self.do_location_scan(
393            SCANS_REQUIRED_TO_FIND_SSID, self.ap_a[wutils.WifiEnums.SSID_KEY])
394        time.sleep(WIFI_TOGGLE_DELAY_SEC)
395        asserts.assert_true(
396            self.dut.droid.wifiCheckState(),
397            "Expect Wifi Wake to enable Wifi, but Wifi is disabled.")
398
399    @test_tracker_info(uuid="d67657c8-3de3-46a6-a103-428cdab89423")
400    def test_reconnect_to_better_saved_network(self):
401        """
402        2 saved APs, one attenuated, one unattenuated, Wifi Wake should connect
403        to the unattenuated AP
404        """
405        wutils.wifi_connect(self.dut, self.ap_a, num_of_tries=5)
406        wutils.wifi_connect(self.dut, self.ap_b, num_of_tries=5)
407        self.dut.ed.clear_all_events()
408        self.ap_a_off()
409        self.ap_b_off()
410        wutils.wait_for_disconnect(self.dut, DISCONNECT_TIMEOUT_SEC)
411        self.log.info("Wifi Disconnected")
412
413        if "google_pixel_watch_models" in self.user_params:
414            if self.dut.model in self.user_params["google_pixel_watch_models"]:
415                wutils.disable_wear_wifimediator(self.dut, True)
416
417        self.do_location_scan(2)
418        time.sleep(LAST_DISCONNECT_TIMEOUT_SEC * 1.2)
419        wutils.wifi_toggle_state(self.dut, new_state=False)
420        time.sleep(PRESCAN_DELAY_SEC)
421        self.do_location_scan(2 * CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT + 2)
422
423        self.ap_a_on()
424        self.ap_b_on()
425        self.ap_a_atten.set_atten(30)
426        self.ap_b_atten.set_atten(0)
427
428        if "google_pixel_watch_models" in self.user_params:
429            if self.dut.model in self.user_params["google_pixel_watch_models"]:
430                wutils.disable_wear_wifimediator(self.dut, False)
431
432        self.do_location_scan(
433            SCANS_REQUIRED_TO_FIND_SSID, self.ap_b[wutils.WifiEnums.SSID_KEY])
434        time.sleep(WIFI_TOGGLE_DELAY_SEC)
435        asserts.assert_true(
436            self.dut.droid.wifiCheckState(),
437            "Expect Wifi Wake to enable Wifi, but Wifi is disabled.")
438        expected_ssid = self.ap_b[wutils.WifiEnums.SSID_KEY]
439        wutils.wait_for_connect(self.dut, expected_ssid)
440