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