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