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 itertools
18import pprint
19import queue
20import time
21
22import acts.base_test
23import acts.signals as signals
24import acts.test_utils.wifi.wifi_test_utils as wutils
25import acts.utils
26
27from acts import asserts
28from acts.controllers.android_device import SL4A_APK_NAME
29from acts.test_decorators import test_tracker_info
30from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
31from acts.test_utils.wifi import wifi_constants
32
33WifiEnums = wutils.WifiEnums
34# EAP Macros
35EAP = WifiEnums.Eap
36EapPhase2 = WifiEnums.EapPhase2
37# Enterprise Config Macros
38Ent = WifiEnums.Enterprise
39
40# Default timeout used for reboot, toggle WiFi and Airplane mode,
41# for the system to settle down after the operation.
42DEFAULT_TIMEOUT = 10
43
44
45class WifiNetworkSuggestionTest(WifiBaseTest):
46    """Tests for WifiNetworkSuggestion API surface.
47
48    Test Bed Requirement:
49    * one Android device
50    * Several Wi-Fi networks visible to the device, including an open Wi-Fi
51      network.
52    """
53
54    def setup_class(self):
55        super().setup_class()
56
57        self.dut = self.android_devices[0]
58        wutils.wifi_test_device_init(self.dut)
59        req_params = []
60        opt_param = [
61            "open_network", "reference_networks", "radius_conf_2g", "radius_conf_5g", "ca_cert",
62            "eap_identity", "eap_password", "hidden_networks"
63        ]
64        self.unpack_userparams(
65            req_param_names=req_params, opt_param_names=opt_param)
66
67        if "AccessPoint" in self.user_params:
68            self.legacy_configure_ap_and_start(
69                wpa_network=True, ent_network=True,
70                radius_conf_2g=self.radius_conf_2g,
71                radius_conf_5g=self.radius_conf_5g,)
72
73        asserts.assert_true(
74            len(self.reference_networks) > 0,
75            "Need at least one reference network with psk.")
76        if hasattr(self, "reference_networks"):
77            self.wpa_psk_2g = self.reference_networks[0]["2g"]
78            self.wpa_psk_5g = self.reference_networks[0]["5g"]
79        if hasattr(self, "open_network"):
80            self.open_2g = self.open_network[0]["2g"]
81            self.open_5g = self.open_network[0]["5g"]
82        if hasattr(self, "ent_networks"):
83            self.ent_network_2g = self.ent_networks[0]["2g"]
84            self.ent_network_5g = self.ent_networks[0]["5g"]
85            self.config_aka = {
86                Ent.EAP: int(EAP.AKA),
87                WifiEnums.SSID_KEY: self.ent_network_2g[WifiEnums.SSID_KEY],
88            }
89            self.config_ttls = {
90                Ent.EAP: int(EAP.TTLS),
91                Ent.CA_CERT: self.ca_cert,
92                Ent.IDENTITY: self.eap_identity,
93                Ent.PASSWORD: self.eap_password,
94                Ent.PHASE2: int(EapPhase2.MSCHAPV2),
95                WifiEnums.SSID_KEY: self.ent_network_2g[WifiEnums.SSID_KEY],
96            }
97        if hasattr(self, "hidden_networks"):
98            self.hidden_network = self.hidden_networks[0]
99        self.dut.droid.wifiRemoveNetworkSuggestions([])
100
101    def setup_test(self):
102        self.dut.droid.wakeLockAcquireBright()
103        self.dut.droid.wakeUpNow()
104        self.clear_deleted_ephemeral_networks()
105        wutils.wifi_toggle_state(self.dut, True)
106        self.dut.ed.clear_all_events()
107
108    def teardown_test(self):
109        self.dut.droid.wakeLockRelease()
110        self.dut.droid.goToSleepNow()
111        self.dut.droid.wifiRemoveNetworkSuggestions([])
112        self.dut.droid.wifiDisconnect()
113        wutils.reset_wifi(self.dut)
114        wutils.wifi_toggle_state(self.dut, False)
115        self.dut.ed.clear_all_events()
116
117    def on_fail(self, test_name, begin_time):
118        self.dut.take_bug_report(test_name, begin_time)
119        self.dut.cat_adb_log(test_name, begin_time)
120
121    def teardown_class(self):
122        if "AccessPoint" in self.user_params:
123            del self.user_params["reference_networks"]
124            del self.user_params["open_network"]
125
126    """Helper Functions"""
127    def set_approved(self, approved):
128        self.dut.log.debug("Setting suggestions from sl4a app "
129                           + "approved" if approved else "not approved")
130        self.dut.adb.shell("cmd wifi network-suggestions-set-user-approved"
131                           + " " + SL4A_APK_NAME
132                           + " " + ("yes" if approved else "no"))
133
134    def is_approved(self):
135        is_approved_str = self.dut.adb.shell(
136            "cmd wifi network-suggestions-has-user-approved"
137            + " " + SL4A_APK_NAME)
138        return True if (is_approved_str == "yes") else False
139
140    def clear_deleted_ephemeral_networks(self):
141        self.dut.log.debug("Clearing deleted ephemeral networks")
142        self.dut.adb.shell(
143            "cmd wifi clear-deleted-ephemeral-networks")
144
145    def add_suggestions_and_ensure_connection(self, network_suggestions,
146                                              expected_ssid,
147                                              expect_post_connection_broadcast):
148        if expect_post_connection_broadcast is not None:
149            self.dut.droid.wifiStartTrackingNetworkSuggestionStateChange()
150
151        self.dut.log.info("Adding network suggestions");
152        asserts.assert_true(
153            self.dut.droid.wifiAddNetworkSuggestions(network_suggestions),
154            "Failed to add suggestions")
155        # Enable suggestions by the app.
156        self.dut.log.debug("Enabling suggestions from test");
157        self.set_approved(True)
158        wutils.start_wifi_connection_scan_and_return_status(self.dut)
159        wutils.wait_for_connect(self.dut, expected_ssid)
160
161        if expect_post_connection_broadcast is None:
162            return;
163
164        # Check if we expected to get the broadcast.
165        try:
166            event = self.dut.ed.pop_event(
167                wifi_constants.WIFI_NETWORK_SUGGESTION_POST_CONNECTION, 60)
168        except queue.Empty:
169            if expect_post_connection_broadcast:
170                raise signals.TestFailure(
171                    "Did not receive post connection broadcast")
172        else:
173            if not expect_post_connection_broadcast:
174                raise signals.TestFailure(
175                    "Received post connection broadcast")
176        finally:
177            self.dut.droid.wifiStopTrackingNetworkSuggestionStateChange()
178        self.dut.ed.clear_all_events()
179
180    def remove_suggestions_disconnect_and_ensure_no_connection_back(self,
181                                                                    network_suggestions,
182                                                                    expected_ssid):
183        self.dut.log.info("Removing network suggestions")
184        asserts.assert_true(
185            self.dut.droid.wifiRemoveNetworkSuggestions(network_suggestions),
186            "Failed to remove suggestions")
187        # Ensure we did not disconnect
188        wutils.ensure_no_disconnect(self.dut)
189
190        # Trigger a disconnect and wait for the disconnect.
191        self.dut.droid.wifiDisconnect()
192        wutils.wait_for_disconnect(self.dut)
193        self.dut.ed.clear_all_events()
194
195        # Now ensure that we didn't connect back.
196        asserts.assert_false(
197            wutils.wait_for_connect(self.dut, expected_ssid, assert_on_fail=False),
198            "Device should not connect back")
199
200    def _test_connect_to_wifi_network_reboot_config_store(self,
201                                                          network_suggestions,
202                                                          wifi_network):
203        """ Test network suggestion with reboot config store
204
205        Args:
206        1. network_suggestions: network suggestions in list to add to the device.
207        2. wifi_network: expected wifi network to connect to
208        """
209
210        self.add_suggestions_and_ensure_connection(
211            network_suggestions, wifi_network[WifiEnums.SSID_KEY], None)
212
213        # Reboot and wait for connection back to the same suggestion.
214        self.dut.reboot()
215        time.sleep(DEFAULT_TIMEOUT)
216
217        wutils.wait_for_connect(self.dut, wifi_network[WifiEnums.SSID_KEY])
218
219        self.remove_suggestions_disconnect_and_ensure_no_connection_back(
220            network_suggestions, wifi_network[WifiEnums.SSID_KEY])
221
222    @test_tracker_info(uuid="bda8ed20-4382-4380-831a-64cf77eca108")
223    def test_connect_to_wpa_psk_2g(self):
224        """ Adds a network suggestion and ensure that the device connected.
225
226        Steps:
227        1. Send a network suggestion to the device.
228        2. Wait for the device to connect to it.
229        3. Ensure that we did not receive the post connection broadcast
230           (isAppInteractionRequired = False).
231        4. Remove the suggestions and ensure the device does not connect back.
232        """
233        self.add_suggestions_and_ensure_connection(
234            [self.wpa_psk_2g], self.wpa_psk_2g[WifiEnums.SSID_KEY],
235            False)
236
237        self.remove_suggestions_disconnect_and_ensure_no_connection_back(
238            [self.wpa_psk_2g], self.wpa_psk_2g[WifiEnums.SSID_KEY])
239
240    @test_tracker_info(uuid="f54bc250-d9e9-4f00-8b5b-b866e8550b43")
241    def test_connect_to_highest_priority(self):
242        """
243        Adds network suggestions and ensures that device connects to
244        the suggestion with the highest priority.
245
246        Steps:
247        1. Send 2 network suggestions to the device (with different priorities).
248        2. Wait for the device to connect to the network with the highest
249           priority.
250        3. Re-add the suggestions with the priorities reversed.
251        4. Again wait for the device to connect to the network with the highest
252           priority.
253        """
254        network_suggestion_2g = self.wpa_psk_2g
255        network_suggestion_5g = self.wpa_psk_5g
256
257        # Add suggestions & wait for the connection event.
258        network_suggestion_2g[WifiEnums.PRIORITY] = 5
259        network_suggestion_5g[WifiEnums.PRIORITY] = 2
260        self.add_suggestions_and_ensure_connection(
261            [network_suggestion_2g, network_suggestion_5g],
262            self.wpa_psk_2g[WifiEnums.SSID_KEY],
263            None)
264
265        self.remove_suggestions_disconnect_and_ensure_no_connection_back(
266            [], self.wpa_psk_2g[WifiEnums.SSID_KEY])
267
268        # Reverse the priority.
269        # Add suggestions & wait for the connection event.
270        network_suggestion_2g[WifiEnums.PRIORITY] = 2
271        network_suggestion_5g[WifiEnums.PRIORITY] = 5
272        self.add_suggestions_and_ensure_connection(
273            [network_suggestion_2g, network_suggestion_5g],
274            self.wpa_psk_5g[WifiEnums.SSID_KEY],
275            None)
276
277    @test_tracker_info(uuid="b1d27eea-23c8-4c4f-b944-ef118e4cc35f")
278    def test_connect_to_wpa_psk_2g_with_post_connection_broadcast(self):
279        """ Adds a network suggestion and ensure that the device connected.
280
281        Steps:
282        1. Send a network suggestion to the device with
283           isAppInteractionRequired set.
284        2. Wait for the device to connect to it.
285        3. Ensure that we did receive the post connection broadcast
286           (isAppInteractionRequired = True).
287        4. Remove the suggestions and ensure the device does not connect back.
288        """
289        network_suggestion = self.wpa_psk_2g
290        network_suggestion[WifiEnums.IS_APP_INTERACTION_REQUIRED] = True
291        self.add_suggestions_and_ensure_connection(
292            [network_suggestion], self.wpa_psk_2g[WifiEnums.SSID_KEY],
293            True)
294        self.remove_suggestions_disconnect_and_ensure_no_connection_back(
295            [self.wpa_psk_2g], self.wpa_psk_2g[WifiEnums.SSID_KEY])
296
297    @test_tracker_info(uuid="a036a24d-29c0-456d-ae6a-afdde34da710")
298    def test_connect_to_wpa_psk_5g_reboot_config_store(self):
299        """
300        Adds a network suggestion and ensure that the device connects to it
301        after reboot.
302
303        Steps:
304        1. Send a network suggestion to the device.
305        2. Wait for the device to connect to it.
306        3. Ensure that we did not receive the post connection broadcast
307           (isAppInteractionRequired = False).
308        4. Reboot the device.
309        5. Wait for the device to connect to back to it.
310        6. Remove the suggestions and ensure the device does not connect back.
311        """
312        self._test_connect_to_wifi_network_reboot_config_store(
313            [self.wpa_psk_5g], self.wpa_psk_5g)
314
315    @test_tracker_info(uuid="61649a2b-0f00-4272-9b9b-40ad5944da31")
316    def test_connect_to_wpa_ent_config_aka_reboot_config_store(self):
317        """
318        Adds a network suggestion and ensure that the device connects to it
319        after reboot.
320
321        Steps:
322        1. Send a Enterprise AKA network suggestion to the device.
323        2. Wait for the device to connect to it.
324        3. Ensure that we did not receive the post connection broadcast.
325        4. Reboot the device.
326        5. Wait for the device to connect to the wifi network.
327        6. Remove suggestions and ensure device doesn't connect back to it.
328        """
329        self._test_connect_to_wifi_network_reboot_config_store(
330            [self.config_aka], self.ent_network_2g)
331
332    @test_tracker_info(uuid="98b2d40a-acb4-4a2f-aba1-b069e2a1d09d")
333    def test_connect_to_wpa_ent_config_ttls_pap_reboot_config_store(self):
334        """
335        Adds a network suggestion and ensure that the device connects to it
336        after reboot.
337
338        Steps:
339        1. Send a Enterprise TTLS PAP network suggestion to the device.
340        2. Wait for the device to connect to it.
341        3. Ensure that we did not receive the post connection broadcast.
342        4. Reboot the device.
343        5. Wait for the device to connect to the wifi network.
344        6. Remove suggestions and ensure device doesn't connect back to it.
345        """
346        config = dict(self.config_ttls)
347        config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.PAP.value
348
349        self._test_connect_to_wifi_network_reboot_config_store(
350            [config], self.ent_network_2g)
351
352    @test_tracker_info(uuid="554b5861-22d0-4922-a5f4-712b4cf564eb")
353    def test_fail_to_connect_to_wpa_psk_5g_when_not_approved(self):
354        """
355        Adds a network suggestion and ensure that the device does not
356        connect to it until we approve the app.
357
358        Steps:
359        1. Send a network suggestion to the device with the app not approved.
360        2. Ensure the network is present in scan results, but we don't connect
361           to it.
362        3. Now approve the app.
363        4. Wait for the device to connect to it.
364        """
365        self.dut.log.info("Adding network suggestions");
366        asserts.assert_true(
367            self.dut.droid.wifiAddNetworkSuggestions([self.wpa_psk_5g]),
368            "Failed to add suggestions")
369
370        # Disable suggestions by the app.
371        self.set_approved(False)
372
373        # Ensure the app is not approved.
374        asserts.assert_false(
375            self.is_approved(),
376            "Suggestions should be disabled")
377
378        # Start a new scan to trigger auto-join.
379        wutils.start_wifi_connection_scan_and_ensure_network_found(
380            self.dut, self.wpa_psk_5g[WifiEnums.SSID_KEY])
381
382        # Ensure we don't connect to the network.
383        asserts.assert_false(
384            wutils.wait_for_connect(
385                self.dut, self.wpa_psk_5g[WifiEnums.SSID_KEY], assert_on_fail=False),
386            "Should not connect to network suggestions from unapproved app")
387
388        self.dut.log.info("Enabling suggestions from test");
389        # Now Enable suggestions by the app & ensure we connect to the network.
390        self.set_approved(True)
391
392        # Ensure the app is approved.
393        asserts.assert_true(
394            self.is_approved(),
395            "Suggestions should be enabled")
396
397        # Start a new scan to trigger auto-join.
398        wutils.start_wifi_connection_scan_and_ensure_network_found(
399            self.dut, self.wpa_psk_5g[WifiEnums.SSID_KEY])
400
401        wutils.wait_for_connect(self.dut, self.wpa_psk_5g[WifiEnums.SSID_KEY])
402
403    @test_tracker_info(uuid="98400dea-776e-4a0a-9024-18845b27331c")
404    def test_fail_to_connect_to_wpa_psk_2g_after_user_forgot_network(self):
405        """
406        Adds a network suggestion and ensures that the device does not
407        connect to it after the user forgot the network previously.
408
409        Steps:
410        1. Send a network suggestion to the device with
411           isAppInteractionRequired set.
412        2. Wait for the device to connect to it.
413        3. Ensure that we did receive the post connection broadcast
414           (isAppInteractionRequired = True).
415        4. Simulate user forgetting the network and the device does not
416           connecting back even though the suggestion is active from the app.
417        """
418        network_suggestion = self.wpa_psk_2g
419        network_suggestion[WifiEnums.IS_APP_INTERACTION_REQUIRED] = True
420        self.add_suggestions_and_ensure_connection(
421            [network_suggestion], self.wpa_psk_2g[WifiEnums.SSID_KEY],
422            True)
423
424        # Simulate user forgetting the ephemeral network.
425        self.dut.droid.wifiDisableEphemeralNetwork(
426            self.wpa_psk_2g[WifiEnums.SSID_KEY])
427        wutils.wait_for_disconnect(self.dut)
428        self.dut.log.info("Disconnected from network %s", self.wpa_psk_2g)
429        self.dut.ed.clear_all_events()
430
431        # Now ensure that we don't connect back even though the suggestion
432        # is still active.
433        asserts.assert_false(
434            wutils.wait_for_connect(self.dut,
435                                    self.wpa_psk_2g[WifiEnums.SSID_KEY],
436                                    assert_on_fail=False),
437            "Device should not connect back")
438
439    @test_tracker_info(uuid="93c86b05-fa56-4d79-ad27-009a16f691b1")
440    def test_connect_to_hidden_network(self):
441        """
442        Adds a network suggestion with hidden SSID config, ensure device can scan
443        and connect to this network.
444
445        Steps:
446        1. Send a hidden network suggestion to the device.
447        2. Wait for the device to connect to it.
448        3. Ensure that we did not receive the post connection broadcast
449           (isAppInteractionRequired = False).
450        4. Remove the suggestions and ensure the device does not connect back.
451        """
452        asserts.skip_if(not hasattr(self, "hidden_networks"), "No hidden networks, skip this test")
453
454        network_suggestion = self.hidden_network
455        self.add_suggestions_and_ensure_connection(
456            [network_suggestion], network_suggestion[WifiEnums.SSID_KEY], False)
457        self.remove_suggestions_disconnect_and_ensure_no_connection_back(
458            [network_suggestion], network_suggestion[WifiEnums.SSID_KEY])
459