1#!/usr/bin/env python3.4
2#
3#   Copyright 2017 - 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.test_utils.wifi.wifi_test_utils as wutils
24
25
26import WifiManagerTest
27from acts import asserts
28from acts import signals
29from acts.libs.uicd.uicd_cli import UicdCli
30from acts.libs.uicd.uicd_cli import UicdError
31from acts.test_decorators import test_tracker_info
32from acts.test_utils.tel.tel_test_utils import get_operator_name
33from acts.utils import force_airplane_mode
34
35WifiEnums = wutils.WifiEnums
36
37DEFAULT_TIMEOUT = 10
38OSU_TEST_TIMEOUT = 300
39
40# Constants for providers.
41GLOBAL_RE = 0
42OSU_BOINGO = 0
43BOINGO = 1
44ATT = 2
45
46# Constants used for various device operations.
47RESET = 1
48TOGGLE = 2
49
50UNKNOWN_FQDN = "@#@@!00fffffx"
51
52class WifiPasspointTest(acts.base_test.BaseTestClass):
53    """Tests for APIs in Android's WifiManager class.
54
55    Test Bed Requirement:
56    * One Android device
57    * Several Wi-Fi networks visible to the device, including an open Wi-Fi
58      network.
59    """
60
61    def setup_class(self):
62        self.dut = self.android_devices[0]
63        wutils.wifi_test_device_init(self.dut)
64        req_params = ["passpoint_networks", "uicd_workflows", "uicd_zip"]
65        opt_param = []
66        self.unpack_userparams(
67            req_param_names=req_params, opt_param_names=opt_param)
68        self.unpack_userparams(req_params)
69        asserts.assert_true(
70            len(self.passpoint_networks) > 0,
71            "Need at least one Passpoint network.")
72        wutils.wifi_toggle_state(self.dut, True)
73        self.unknown_fqdn = UNKNOWN_FQDN
74        # Setup Uicd cli object for UI interation.
75        self.ui = UicdCli(self.uicd_zip[0], self.uicd_workflows)
76        self.passpoint_workflow = "passpoint-login_%s" % self.dut.model
77
78
79    def setup_test(self):
80        self.dut.droid.wakeLockAcquireBright()
81        self.dut.droid.wakeUpNow()
82        self.dut.unlock_screen()
83
84
85    def teardown_test(self):
86        self.dut.droid.wakeLockRelease()
87        self.dut.droid.goToSleepNow()
88        passpoint_configs = self.dut.droid.getPasspointConfigs()
89        for config in passpoint_configs:
90            wutils.delete_passpoint(self.dut, config)
91        wutils.reset_wifi(self.dut)
92
93
94    def on_fail(self, test_name, begin_time):
95        self.dut.take_bug_report(test_name, begin_time)
96
97
98    """Helper Functions"""
99
100
101    def install_passpoint_profile(self, passpoint_config):
102        """Install the Passpoint network Profile.
103
104        Args:
105            passpoint_config: A JSON dict of the Passpoint configuration.
106
107        """
108        asserts.assert_true(WifiEnums.SSID_KEY in passpoint_config,
109                "Key '%s' must be present in network definition." %
110                WifiEnums.SSID_KEY)
111        # Install the Passpoint profile.
112        self.dut.droid.addUpdatePasspointConfig(passpoint_config)
113
114
115    def check_passpoint_connection(self, passpoint_network):
116        """Verify the device is automatically able to connect to the Passpoint
117           network.
118
119           Args:
120               passpoint_network: SSID of the Passpoint network.
121
122        """
123        ad = self.dut
124        ad.ed.clear_all_events()
125        try:
126            wutils.start_wifi_connection_scan_and_return_status(ad)
127            wutils.wait_for_connect(ad)
128        except:
129            pass
130        # Re-verify we are connected to the correct network.
131        network_info = self.dut.droid.wifiGetConnectionInfo()
132        self.log.info("Network Info: %s" % network_info)
133        if not network_info or not network_info[WifiEnums.SSID_KEY] or \
134            network_info[WifiEnums.SSID_KEY] not in passpoint_network:
135              raise signals.TestFailure(
136                  "Device did not connect to passpoint network.")
137
138
139    def get_configured_passpoint_and_delete(self):
140        """Get configured Passpoint network and delete using its FQDN."""
141        passpoint_config = self.dut.droid.getPasspointConfigs()
142        if not len(passpoint_config):
143            raise signals.TestFailure("Failed to fetch the list of configured"
144                                      "passpoint networks.")
145        if not wutils.delete_passpoint(self.dut, passpoint_config[0]):
146            raise signals.TestFailure("Failed to delete Passpoint configuration"
147                                      " with FQDN = %s" % passpoint_config[0])
148
149    def start_subscription_provisioning(self, state):
150        """Start subscription provisioning with a default provider."""
151
152        self.unpack_userparams(('osu_configs',))
153        asserts.assert_true(
154            len(self.osu_configs) > 0,
155            "Need at least one osu config.")
156        osu_config = self.osu_configs[OSU_BOINGO]
157        # Clear all previous events.
158        self.dut.ed.clear_all_events()
159        self.dut.droid.startSubscriptionProvisioning(osu_config)
160        start_time = time.time()
161        while time.time() < start_time + OSU_TEST_TIMEOUT:
162            dut_event = self.dut.ed.pop_event("onProvisioningCallback",
163                                              DEFAULT_TIMEOUT * 18)
164            if dut_event['data']['tag'] == 'success':
165                self.log.info("Passpoint Provisioning Success")
166                # Reset WiFi after provisioning success.
167                if state == RESET:
168                    wutils.reset_wifi(self.dut)
169                    time.sleep(DEFAULT_TIMEOUT)
170                # Toggle WiFi after provisioning success.
171                elif state == TOGGLE:
172                    wutils.toggle_wifi_off_and_on(self.dut)
173                    time.sleep(DEFAULT_TIMEOUT)
174                break
175            if dut_event['data']['tag'] == 'failure':
176                raise signals.TestFailure(
177                    "Passpoint Provisioning is failed with %s" %
178                    dut_event['data'][
179                        'reason'])
180                break
181            if dut_event['data']['tag'] == 'status':
182                self.log.info(
183                    "Passpoint Provisioning status %s" % dut_event['data'][
184                        'status'])
185                if int(dut_event['data']['status']) == 7:
186                    self.ui.run(self.dut.serial, self.passpoint_workflow)
187        # Clear all previous events.
188        self.dut.ed.clear_all_events()
189
190        # Verify device connects to the Passpoint network.
191        time.sleep(DEFAULT_TIMEOUT)
192        current_passpoint = self.dut.droid.wifiGetConnectionInfo()
193        if current_passpoint[WifiEnums.SSID_KEY] not in osu_config[
194            "expected_ssids"]:
195            raise signals.TestFailure("Device did not connect to the %s"
196                                      " passpoint network" % osu_config[
197                                          "expected_ssids"])
198        # Delete the Passpoint profile.
199        self.get_configured_passpoint_and_delete()
200        wutils.wait_for_disconnect(self.dut)
201
202
203    """Tests"""
204
205    @test_tracker_info(uuid="b0bc0153-77bb-4594-8f19-cea2c6bd2f43")
206    def test_add_passpoint_network(self):
207        """Add a Passpoint network and verify device connects to it.
208
209        Steps:
210            1. Install a Passpoint Profile.
211            2. Verify the device connects to the required Passpoint SSID.
212            3. Get the Passpoint configuration added above.
213            4. Delete Passpoint configuration using its FQDN.
214            5. Verify that we are disconnected from the Passpoint network.
215
216        """
217        passpoint_config = self.passpoint_networks[BOINGO]
218        self.install_passpoint_profile(passpoint_config)
219        ssid = passpoint_config[WifiEnums.SSID_KEY]
220        self.check_passpoint_connection(ssid)
221        self.get_configured_passpoint_and_delete()
222        wutils.wait_for_disconnect(self.dut)
223
224
225    @test_tracker_info(uuid="eb29d6e2-a755-4c9c-9e4e-63ea2277a64a")
226    def test_update_passpoint_network(self):
227        """Update a previous Passpoint network and verify device still connects
228           to it.
229
230        1. Install a Passpoint Profile.
231        2. Verify the device connects to the required Passpoint SSID.
232        3. Update the Passpoint Profile.
233        4. Verify device is still connected to the Passpoint SSID.
234        5. Get the Passpoint configuration added above.
235        6. Delete Passpoint configuration using its FQDN.
236
237        """
238        passpoint_config = self.passpoint_networks[BOINGO]
239        self.install_passpoint_profile(passpoint_config)
240        ssid = passpoint_config[WifiEnums.SSID_KEY]
241        self.check_passpoint_connection(ssid)
242
243        # Update passpoint configuration using the original profile because we
244        # do not have real profile with updated credentials to use.
245        self.install_passpoint_profile(passpoint_config)
246
247        # Wait for a Disconnect event from the supplicant.
248        wutils.wait_for_disconnect(self.dut)
249
250        # Now check if we are again connected with the updated profile.
251        self.check_passpoint_connection(ssid)
252
253        self.get_configured_passpoint_and_delete()
254        wutils.wait_for_disconnect(self.dut)
255
256
257    @test_tracker_info(uuid="b6e8068d-faa1-49f2-b421-c60defaed5f0")
258    def test_add_delete_list_of_passpoint_network(self):
259        """Add multiple passpoint networks, list them and delete one by one.
260
261        1. Install Passpoint Profile A.
262        2. Install Passpoint Profile B.
263        3. Get all the Passpoint configurations added above and verify.
264        6. Ensure all Passpoint configurations can be deleted.
265
266        """
267        for passpoint_config in self.passpoint_networks[:2]:
268            self.install_passpoint_profile(passpoint_config)
269            time.sleep(DEFAULT_TIMEOUT)
270        configs = self.dut.droid.getPasspointConfigs()
271        #  It is length -1 because ATT profile will be handled separately
272        if not len(configs) or len(configs) != len(self.passpoint_networks[:2]):
273            raise signals.TestFailure("Failed to fetch some or all of the"
274                                      " configured passpoint networks.")
275        for config in configs:
276            if not wutils.delete_passpoint(self.dut, config):
277                raise signals.TestFailure("Failed to delete Passpoint"
278                                          " configuration with FQDN = %s" %
279                                          config)
280
281
282    @test_tracker_info(uuid="a53251be-7aaf-41fc-a5f3-63984269d224")
283    def test_delete_unknown_fqdn(self):
284        """Negative test to delete Passpoint profile using an unknown FQDN.
285
286        1. Pass an unknown FQDN for removal.
287        2. Verify that it was not successful.
288
289        """
290        if wutils.delete_passpoint(self.dut, self.unknown_fqdn):
291            raise signals.TestFailure("Failed because an unknown FQDN"
292                                      " was successfully deleted.")
293
294
295    @test_tracker_info(uuid="bf03c03a-e649-4e2b-a557-1f791bd98951")
296    def test_passpoint_failover(self):
297        """Add a pair of passpoint networks and test failover when one of the"
298           profiles is removed.
299
300        1. Install a Passpoint Profile A and B.
301        2. Verify device connects to a Passpoint network and get SSID.
302        3. Delete the current Passpoint profile using its FQDN.
303        4. Verify device fails over and connects to the other Passpoint SSID.
304        5. Delete Passpoint configuration using its FQDN.
305
306        """
307        # Install both Passpoint profiles on the device.
308        passpoint_ssid = list()
309        for passpoint_config in self.passpoint_networks[:2]:
310            passpoint_ssid.append(passpoint_config[WifiEnums.SSID_KEY])
311            self.install_passpoint_profile(passpoint_config)
312            time.sleep(DEFAULT_TIMEOUT)
313
314        # Get the current network and the failover network.
315        wutils.wait_for_connect(self.dut)
316        current_passpoint = self.dut.droid.wifiGetConnectionInfo()
317        current_ssid = current_passpoint[WifiEnums.SSID_KEY]
318        if current_ssid not in passpoint_ssid:
319           raise signals.TestFailure("Device did not connect to any of the "
320                                     "configured Passpoint networks.")
321
322        expected_ssid =  self.passpoint_networks[0][WifiEnums.SSID_KEY]
323        if current_ssid == expected_ssid:
324            expected_ssid = self.passpoint_networks[1][WifiEnums.SSID_KEY]
325
326        # Remove the current Passpoint profile.
327        for network in self.passpoint_networks[:2]:
328            if network[WifiEnums.SSID_KEY] == current_ssid:
329                if not wutils.delete_passpoint(self.dut, network["fqdn"]):
330                    raise signals.TestFailure("Failed to delete Passpoint"
331                                              " configuration with FQDN = %s" %
332                                              network["fqdn"])
333        # Verify device fails over and connects to the other passpoint network.
334        time.sleep(DEFAULT_TIMEOUT)
335
336        current_passpoint = self.dut.droid.wifiGetConnectionInfo()
337        if current_passpoint[WifiEnums.SSID_KEY] != expected_ssid:
338            raise signals.TestFailure("Device did not failover to the %s"
339                                      " passpoint network" % expected_ssid)
340
341        # Delete the remaining Passpoint profile.
342        self.get_configured_passpoint_and_delete()
343        wutils.wait_for_disconnect(self.dut)
344
345
346    @test_tracker_info(uuid="37ae0223-0cb7-43f3-8ba8-474fad6e4b71")
347    def test_install_att_passpoint_profile(self):
348        """Add an AT&T Passpoint profile.
349
350        It is used for only installing the profile for other tests.
351        """
352        isFound = False
353        for passpoint_config in self.passpoint_networks:
354            if 'att' in passpoint_config['fqdn']:
355                isFound = True
356                self.install_passpoint_profile(passpoint_config)
357                break
358        if not isFound:
359            raise signals.TestFailure("cannot find ATT profile.")
360
361
362    @test_tracker_info(uuid="e3e826d2-7c39-4c37-ab3f-81992d5aa0e8")
363    def test_att_passpoint_network(self):
364        """Add a AT&T Passpoint network and verify device connects to it.
365
366        Steps:
367            1. Install a AT&T Passpoint Profile.
368            2. Verify the device connects to the required Passpoint SSID.
369            3. Get the Passpoint configuration added above.
370            4. Delete Passpoint configuration using its FQDN.
371            5. Verify that we are disconnected from the Passpoint network.
372
373        """
374        carriers = ["att"]
375        operator = get_operator_name(self.log, self.dut)
376        asserts.skip_if(operator not in carriers,
377                        "Device %s does not have a ATT sim" % self.dut.model)
378
379        passpoint_config = self.passpoint_networks[ATT]
380        self.install_passpoint_profile(passpoint_config)
381        ssid = passpoint_config[WifiEnums.SSID_KEY]
382        self.check_passpoint_connection(ssid)
383        self.get_configured_passpoint_and_delete()
384        wutils.wait_for_disconnect(self.dut)
385
386
387    @test_tracker_info(uuid="c85c81b2-7133-4635-8328-9498169ae802")
388    def test_start_subscription_provisioning(self):
389        self.start_subscription_provisioning(0)
390
391
392    @test_tracker_info(uuid="fd09a643-0d4b-45a9-881a-a771f9707ab1")
393    def test_start_subscription_provisioning_and_reset_wifi(self):
394        self.start_subscription_provisioning(RESET)
395
396
397    @test_tracker_info(uuid="f43ea759-673f-4567-aa11-da3bc2cabf08")
398    def test_start_subscription_provisioning_and_toggle_wifi(self):
399        self.start_subscription_provisioning(TOGGLE)
400
401    @test_tracker_info(uuid="ad6d5eb8-a3c5-4ce0-9e10-d0f201cd0f40")
402    def test_user_override_auto_join_on_passpoint_network(self):
403        """Add a Passpoint network, simulate user change the auto join to false, ensure the device
404        doesn't auto connect to this passponit network
405
406        Steps:
407            1. Install a Passpoint Profile.
408            2. Verify the device connects to the required Passpoint SSID.
409            3. Disable auto join Passpoint configuration using its FQDN.
410            4. disable and enable Wifi toggle, ensure we don't connect back
411        """
412        passpoint_config = self.passpoint_networks[BOINGO]
413        self.install_passpoint_profile(passpoint_config)
414        ssid = passpoint_config[WifiEnums.SSID_KEY]
415        self.check_passpoint_connection(ssid)
416        self.dut.log.info("Disable auto join on passpoint")
417        self.dut.droid.wifiEnableAutojoinPasspoint(passpoint_config['fqdn'], False)
418        wutils.wifi_toggle_state(self.dut, False)
419        wutils.wifi_toggle_state(self.dut, True)
420        asserts.assert_false(
421            wutils.wait_for_connect(self.dut, ssid, assert_on_fail=False),
422            "Device should not connect.")
423