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