1"""Tests for Bluetooth PAN profile functionalities."""
2
3import contextlib
4import time
5
6from mobly import test_runner
7from mobly import signals
8from mobly.controllers.android_device_lib import jsonrpc_client_base
9from blueberry.utils import blueberry_base_test
10from blueberry.utils import bt_test_utils
11
12# Timeout to wait for NAP service connection to be specific state in second.
13CONNECTION_TIMEOUT_SECS = 20
14
15# Interval time between ping requests in second.
16PING_INTERVAL_TIME_SEC = 2
17
18# Timeout to wait for ping success in second.
19PING_TIMEOUT_SEC = 60
20
21# A URL is used to verify internet by ping request.
22TEST_URL = 'http://www.google.com'
23
24# A string representing SIM State is ready.
25SIM_STATE_READY = 'READY'
26
27
28class BluetoothPanTest(blueberry_base_test.BlueberryBaseTest):
29  """Test class for Bluetooth PAN(Personal Area Networking) profile.
30
31  Test internet connection sharing via Bluetooth between two Android devices.
32  One device which is referred to as NAP(Network Access Point) uses Bluetooth
33  tethering to share internet connection with another device which is referred
34  to as PANU(Personal Area Networking User).
35  """
36
37  def __init__(self, configs):
38    super().__init__(configs)
39    self.pan_connect_attempts = None
40
41  def setup_class(self):
42    """Standard Mobly setup class."""
43    super(BluetoothPanTest, self).setup_class()
44    # Number of attempts to initiate connection. 5 attempts as default.
45    self.pan_connect_attempts = self.user_params.get('pan_connect_attempts', 5)
46
47    for device in self.android_devices:
48      device.init_setup()
49      device.sl4a_setup()
50      device.mac_address = device.get_bluetooth_mac_address()
51
52      # Check if the device has inserted a SIM card.
53      if device.sl4a.telephonyGetSimState() != SIM_STATE_READY:
54        raise signals.TestError('SIM card is not ready on Device "%s".' %
55                                device.serial)
56
57    self.primary_device = self.android_devices[0]
58    self.secondary_device = self.android_devices[1]
59
60  def teardown_test(self):
61    """Standard Mobly teardown test.
62
63    Reset every devices when a test finished.
64    """
65    super(BluetoothPanTest, self).teardown_test()
66    # Revert debug tags.
67    for device in self.android_devices:
68      device.debug_tag = device.serial
69      device.factory_reset_bluetooth()
70
71  def wait_for_nap_service_connection(
72      self,
73      device,
74      connected_mac_addr,
75      state_connected=True):
76    """Waits for NAP service connection to be expected state.
77
78    Args:
79      device: AndroidDevice, A device is used to check this connection.
80      connected_mac_addr: String, Bluetooth Mac address is needed to be checked.
81      state_connected: Bool, NAP service connection is established as expected
82          if True, else terminated as expected.
83
84    Raises:
85      TestFailure: Raised if NAP service connection is not expected state.
86    """
87    def is_device_connected():
88      """Returns True if connected else False."""
89      connected_devices = (device.sl4a.
90                           bluetoothPanGetConnectedDevices())
91      # Check if the Bluetooth mac address is in the connected device list.
92      return connected_mac_addr in [d['address'] for d in connected_devices]
93
94    bt_test_utils.wait_until(
95        timeout_sec=CONNECTION_TIMEOUT_SECS,
96        condition_func=is_device_connected,
97        func_args=[],
98        expected_value=state_connected,
99        exception=signals.TestFailure(
100            'NAP service connection failed to be %s in %ds.' %
101            ('established' if state_connected else 'terminated',
102             CONNECTION_TIMEOUT_SECS)))
103
104  def initiate_nap_service_connection(
105      self,
106      initiator_device,
107      connected_mac_addr):
108    """Initiates NAP service connection.
109
110    Args:
111      initiator_device: AndroidDevice, A device intiating connection.
112      connected_mac_addr: String, Bluetooth Mac address of connected device.
113
114    Raises:
115      TestFailure: Raised if NAP service connection fails to be established.
116    """
117    count = 0
118    for _ in range(self.pan_connect_attempts):
119      count += 1
120      try:
121        initiator_device.sl4a.bluetoothConnectBonded(connected_mac_addr)
122        self.wait_for_nap_service_connection(
123            device=initiator_device,
124            connected_mac_addr=connected_mac_addr,
125            state_connected=True)
126        return
127      except signals.TestFailure:
128        if count == self.pan_connect_attempts:
129          raise signals.TestFailure(
130              'NAP service connection still failed to be established '
131              'after retried %d times.' %
132              self.pan_connect_attempts)
133
134  def terminate_nap_service_connection(
135      self,
136      initiator_device,
137      connected_mac_addr):
138    """Terminates NAP service connection.
139
140    Args:
141      initiator_device: AndroidDevice, A device intiating disconnection.
142      connected_mac_addr: String, Bluetooth Mac address of connected device.
143    """
144    initiator_device.log.info('Terminate NAP service connection.')
145    initiator_device.sl4a.bluetoothDisconnectConnected(connected_mac_addr)
146    self.wait_for_nap_service_connection(
147        device=initiator_device,
148        connected_mac_addr=connected_mac_addr,
149        state_connected=False)
150
151  @contextlib.contextmanager
152  def establish_nap_service_connection(self, nap_device, panu_device):
153    """Establishes NAP service connection between both Android devices.
154
155    The context is used to form a basic network connection between devices
156    before executing a test.
157
158    Steps:
159      1. Disable Mobile data to avoid internet access on PANU device.
160      2. Make sure Mobile data available on NAP device.
161      3. Enable Bluetooth from PANU device.
162      4. Enable Bluetooth tethering on NAP device.
163      5. Initiate a connection from PANU device.
164      6. Check if PANU device has internet access via the connection.
165
166    Args:
167      nap_device: AndroidDevice, A device sharing internet connection via
168          Bluetooth tethering.
169      panu_device: AndroidDevice, A device gaining internet access via
170          Bluetooth tethering.
171
172    Yields:
173      None, the context just execute a pre procedure for PAN testing.
174
175    Raises:
176      signals.TestError: raised if a step fails.
177      signals.TestFailure: raised if PANU device fails to access internet.
178    """
179    nap_device.debug_tag = 'NAP'
180    panu_device.debug_tag = 'PANU'
181    try:
182      # Disable Mobile data to avoid internet access on PANU device.
183      panu_device.log.info('Disabling Mobile data...')
184      panu_device.sl4a.setMobileDataEnabled(False)
185      self.verify_internet(
186          allow_access=False,
187          device=panu_device,
188          exception=signals.TestError(
189              'PANU device "%s" still connected to internet when Mobile data '
190              'had been disabled.' % panu_device.serial))
191
192      # Make sure NAP device has Mobile data for internet sharing.
193      nap_device.log.info('Enabling Mobile data...')
194      nap_device.sl4a.setMobileDataEnabled(True)
195      self.verify_internet(
196          allow_access=True,
197          device=nap_device,
198          exception=signals.TestError(
199              'NAP device "%s" did not have internet access when Mobile data '
200              'had been enabled.' % nap_device.serial))
201
202      # Enable Bluetooth tethering from NAP device.
203      nap_device.set_bluetooth_tethering(status_enabled=True)
204      # Wait until Bluetooth tethering stabilizes. This waiting time avoids PANU
205      # device initiates a connection to NAP device immediately when NAP device
206      # enables Bluetooth tethering.
207      time.sleep(5)
208
209      nap_device.activate_pairing_mode()
210      panu_device.log.info('Pair to NAP device "%s".' % nap_device.serial)
211      panu_device.pair_and_connect_bluetooth(nap_device.mac_address)
212
213      # Initiate a connection to NAP device.
214      panu_device.log.info('Initiate a connection to NAP device "%s".' %
215                           nap_device.serial)
216      self.initiate_nap_service_connection(
217          initiator_device=panu_device,
218          connected_mac_addr=nap_device.mac_address)
219
220      # Check if PANU device can access internet via NAP service connection.
221      self.verify_internet(
222          allow_access=True,
223          device=panu_device,
224          exception=signals.TestFailure(
225              'PANU device "%s" failed to access internet via NAP service '
226              'connection.' % panu_device.serial))
227      yield
228    finally:
229      # Disable Bluetooth tethering from NAP device.
230      nap_device.set_bluetooth_tethering(status_enabled=False)
231      panu_device.sl4a.setMobileDataEnabled(True)
232
233  def verify_internet(self, allow_access, device, exception):
234    """Verifies that internet is in expected state.
235
236    Continuously make ping request to a URL for internet verification.
237
238    Args:
239      allow_access: Bool, Device can have internet access as expected if True,
240          else no internet access as expected.
241      device: AndroidDevice, Device to be check internet state.
242      exception: Exception, Raised if internet is not in expected state.
243    """
244    device.log.info('Verify that internet %s be used.' %
245                    ('can' if allow_access else 'can not'))
246
247    def http_ping():
248      """Returns True if http ping success else False."""
249      try:
250        return bool(device.sl4a.httpPing(TEST_URL))
251      except jsonrpc_client_base.ApiError as e:
252        # ApiError is raised by httpPing() when no internet.
253        device.log.debug(str(e))
254      return False
255
256    bt_test_utils.wait_until(
257        timeout_sec=PING_TIMEOUT_SEC,
258        condition_func=http_ping,
259        func_args=[],
260        expected_value=allow_access,
261        exception=exception,
262        interval_sec=PING_INTERVAL_TIME_SEC)
263
264  def test_gain_internet_and_terminate_nap_connection(self):
265    """Test that DUT can access internet and terminate NAP service connection.
266
267    In this test case, primary device is PANU and secondary device is NAP. While
268    a connection has established between both devices, PANU should be able to
269    use internet and terminate the connection to disable internet access.
270
271    Steps:
272      1. Establish NAP service connection between both devices.
273      2. Terminal the connection from PANU device.
274      3. Verify that PANU device cannot access internet.
275    """
276    with self.establish_nap_service_connection(
277        nap_device=self.secondary_device,
278        panu_device=self.primary_device):
279
280      # Terminate the connection from DUT.
281      self.terminate_nap_service_connection(
282          initiator_device=self.primary_device,
283          connected_mac_addr=self.secondary_device.mac_address)
284
285      # Verify that PANU device cannot access internet.
286      self.verify_internet(
287          allow_access=False,
288          device=self.primary_device,
289          exception=signals.TestFailure(
290              'PANU device "%s" can still access internet when it had '
291              'terminated NAP service connection.' %
292              self.primary_device.serial))
293
294  def test_share_internet_and_disable_bluetooth_tethering(self):
295    """Test that DUT can share internet and stop internet sharing.
296
297    In this test case, primary device is NAP and secondary device is PANU. While
298    a connection has established between both devices, NAP should be able to
299    share internet and disable Bluetooth thethering to stop internet sharing.
300
301    Steps:
302      1. Establish NAP service connection between both devices.
303      3. Disable Bluetooth tethering from NAP device.
304      4. Verify that PANU device cannot access internet.
305    """
306    with self.establish_nap_service_connection(
307        nap_device=self.primary_device,
308        panu_device=self.secondary_device):
309
310      # Disable Bluetooth tethering from DUT and check if the nap connection is
311      # terminated.
312      self.primary_device.set_bluetooth_tethering(status_enabled=False)
313      self.wait_for_nap_service_connection(
314          device=self.primary_device,
315          connected_mac_addr=self.secondary_device.mac_address,
316          state_connected=False)
317
318      # Verify that PANU device cannot access internet.
319      self.verify_internet(
320          allow_access=False,
321          device=self.secondary_device,
322          exception=signals.TestFailure(
323              'PANU device "%s" can still access internet when it had '
324              'terminated NAP service connection.' %
325              self.secondary_device.serial))
326
327
328if __name__ == '__main__':
329  test_runner.main()
330