1#/usr/bin/env python3.4 2# 3# Copyright (C) 2016 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); you may not 6# use this file except in compliance with the License. You may obtain a copy of 7# 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, WITHOUT 13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14# License for the specific language governing permissions and limitations under 15# the License. 16"""Test script to test PBAP contact download between two devices which can run SL4A. 17""" 18 19import os 20import time 21 22from acts.test_decorators import test_tracker_info 23from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest 24from acts.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test 25from acts.base_test import BaseTestClass 26from acts.test_utils.bt import bt_contacts_utils 27from acts.test_utils.bt import bt_test_utils 28from acts.test_utils.car import car_bt_utils 29from acts.utils import exe_cmd 30import acts.test_utils.bt.BtEnum as BtEnum 31 32# Offset call logs by 1 minute 33CALL_LOG_TIME_OFFSET_IN_MSEC = 60000 34PSE_CONTACTS_FILE = "psecontacts.vcf" 35PCE_CONTACTS_FILE = "pcecontacts.vcf" 36MERGED_CONTACTS_FILE = "psecombined.vcf" 37STANDART_CONTACT_COUNT = 100 38 39 40class BtCarPbapTest(BluetoothBaseTest): 41 contacts_destination_path = "" 42 43 def __init__(self, controllers): 44 BaseTestClass.__init__(self, controllers) 45 self.pce = self.android_devices[0] 46 self.pse = self.android_devices[1] 47 self.pse2 = self.android_devices[2] 48 self.contacts_destination_path = self.log_path + "/" 49 50 def setup_class(self): 51 if not super(BtCarPbapTest, self).setup_class(): 52 return False 53 permissions_list = [ 54 "android.permission.READ_CONTACTS", 55 "android.permission.WRITE_CONTACTS", 56 "android.permission.READ_EXTERNAL_STORAGE" 57 ] 58 for device in self.android_devices: 59 for permission in permissions_list: 60 device.adb.shell( 61 "pm grant com.google.android.contacts {}".format(permission)) 62 63 # Pair the devices. 64 # This call may block until some specified timeout in bt_test_utils.py. 65 # Grace time inbetween stack state changes 66 67 setup_multiple_devices_for_bt_test(self.android_devices) 68 if not bt_test_utils.pair_pri_to_sec(self.pce, self.pse): 69 self.log.error("Failed to pair.") 70 return False 71 time.sleep(3) 72 if not bt_test_utils.pair_pri_to_sec(self.pce, self.pse2): 73 self.log.error("Failed to pair.") 74 return False 75 76 # Disable the HFP and A2DP profiles. This will ensure only PBAP 77 # gets connected. Also, this will eliminate the auto-connect loop. 78 car_bt_utils.set_car_profile_priorities_off(self.pce, self.pse) 79 car_bt_utils.set_car_profile_priorities_off(self.pce, self.pse2) 80 81 # Enable PBAP on PSE & PCE. 82 83 self.pse.droid.bluetoothChangeProfileAccessPermission( 84 self.pce.droid.bluetoothGetLocalAddress(), 85 BtEnum.BluetoothProfile.PBAP_SERVER.value, 86 BtEnum.BluetoothAccessLevel.ACCESS_ALLOWED.value) 87 88 self.pse2.droid.bluetoothChangeProfileAccessPermission( 89 self.pce.droid.bluetoothGetLocalAddress(), 90 BtEnum.BluetoothProfile.PBAP_SERVER.value, 91 BtEnum.BluetoothAccessLevel.ACCESS_ALLOWED.value) 92 93 bt_test_utils.set_profile_priority( 94 self.pce, self.pse, [BtEnum.BluetoothProfile.PBAP_CLIENT], 95 BtEnum.BluetoothPriorityLevel.PRIORITY_ON) 96 bt_test_utils.set_profile_priority( 97 self.pce, self.pse2, [BtEnum.BluetoothProfile.PBAP_CLIENT], 98 BtEnum.BluetoothPriorityLevel.PRIORITY_ON) 99 100 return True 101 102 def setup_test(self): 103 if not super(BtCarPbapTest, self).setup_test(): 104 return False 105 self.pse.droid.callLogsEraseAll() 106 return self.erase_all_contacts() 107 108 def teardown_test(self): 109 if not super(BtCarPbapTest, self).teardown_test(): 110 return False 111 for device in self.android_devices: 112 bt_contacts_utils.delete_vcf_files(device) 113 114 self.pce.droid.bluetoothPbapClientDisconnect( 115 self.pse.droid.bluetoothGetLocalAddress()) 116 return self.erase_all_contacts() 117 118 def erase_all_contacts(self): 119 try: 120 return all(bt_contacts_utils.erase_contacts(device) for device in self.android_devices) 121 finally: 122 # Allow all content providers to synchronize. 123 time.sleep(1) 124 125 def verify_contacts_match(self): 126 bt_contacts_utils.export_device_contacts_to_vcf( 127 self.pce, self.contacts_destination_path, PCE_CONTACTS_FILE) 128 return bt_contacts_utils.count_contacts_with_differences( 129 self.contacts_destination_path, PCE_CONTACTS_FILE, 130 PSE_CONTACTS_FILE) == 0 131 132 def connect_and_verify(self, count): 133 bt_test_utils.connect_pri_to_sec( 134 self.pce, self.pse, 135 set([BtEnum.BluetoothProfile.PBAP_CLIENT.value])) 136 bt_contacts_utils.wait_for_phone_number_update_complete(self.pce, 137 count) 138 contacts_added = self.verify_contacts_match() 139 self.pce.droid.bluetoothPbapClientDisconnect( 140 self.pse.droid.bluetoothGetLocalAddress()) 141 contacts_removed = bt_contacts_utils.wait_for_phone_number_update_complete( 142 self.pce, 0) 143 return contacts_added and contacts_removed 144 145 @test_tracker_info(uuid='7dcdecfc-42d1-4f41-b66e-823c8f161356') 146 @BluetoothBaseTest.bt_test_wrap 147 def test_pbap_connect_and_disconnect(self): 148 """Test Connectivity 149 150 Test connecting with the server enabled and disabled 151 152 Precondition: 153 1. Devices are paired. 154 155 Steps: 156 1. Disable permission on PSE to prevent PCE from connecting 157 2. Attempt to connect PCE to PSE 158 3. Verify connection failed 159 4. Enable permission on PSE to allow PCE to connect 160 5. Attempt to connect PCE to PSE 161 6. Verify connection succeeded 162 163 Returns: 164 Pass if True 165 Fail if False 166 """ 167 self.pse.droid.bluetoothChangeProfileAccessPermission( 168 self.pce.droid.bluetoothGetLocalAddress(), 169 BtEnum.BluetoothProfile.PBAP_SERVER.value, 170 BtEnum.BluetoothAccessLevel.ACCESS_DENIED.value) 171 if bt_test_utils.connect_pri_to_sec( 172 self.pce, self.pse, 173 set([BtEnum.BluetoothProfile.PBAP_CLIENT.value])): 174 self.log.error("Client connected and shouldn't be.") 175 return False 176 177 self.pce.droid.bluetoothPbapClientDisconnect( 178 self.pse.droid.bluetoothGetLocalAddress()) 179 180 self.pse.droid.bluetoothChangeProfileAccessPermission( 181 self.pce.droid.bluetoothGetLocalAddress(), 182 BtEnum.BluetoothProfile.PBAP_SERVER.value, 183 BtEnum.BluetoothAccessLevel.ACCESS_ALLOWED.value) 184 185 if not bt_test_utils.connect_pri_to_sec( 186 self.pce, self.pse, 187 set([BtEnum.BluetoothProfile.PBAP_CLIENT.value])): 188 self.log.error("No client connected and should be.") 189 return False 190 191 return True 192 193 @test_tracker_info(uuid='1733efb9-71af-4956-bd3a-0d3167d94d0c') 194 @BluetoothBaseTest.bt_test_wrap 195 def test_contact_download(self): 196 """Test Contact Download 197 198 Test download of contacts from a clean state. 199 200 Precondition: 201 1. Devices are paired. 202 203 Steps: 204 1. Erase contacts from PSE and PCE. 205 2. Add a predefined list of contacts to PSE. 206 3. Connect PCE to PSE to perform transfer. 207 4. Compare transfered contacts. 208 5. Disconnect. 209 6. Verify PCE cleaned up contact list. 210 211 Returns: 212 Pass if True 213 Fail if False 214 """ 215 bt_contacts_utils.generate_contact_list(self.contacts_destination_path, 216 PSE_CONTACTS_FILE, 100) 217 phone_numbers_added = bt_contacts_utils.import_device_contacts_from_vcf( 218 self.pse, self.contacts_destination_path, PSE_CONTACTS_FILE) 219 bt_test_utils.connect_pri_to_sec( 220 self.pce, self.pse, 221 set([BtEnum.BluetoothProfile.PBAP_CLIENT.value])) 222 bt_contacts_utils.wait_for_phone_number_update_complete( 223 self.pce, phone_numbers_added) 224 if not self.verify_contacts_match(): 225 return False 226 return bt_contacts_utils.erase_contacts(self.pce) 227 228 @test_tracker_info(uuid='99dc6ac6-b7cf-45ce-927b-8c4ebf8ab664') 229 @BluetoothBaseTest.bt_test_wrap 230 def test_modify_phonebook(self): 231 """Test Modify Phonebook 232 233 Test changing contacts and reconnecting PBAP. 234 235 Precondition: 236 1. Devices are paired. 237 238 Steps: 239 1. Add a predefined list of contacts to PSE. 240 2. Connect PCE to PSE to perform transfer. 241 3. Verify that contacts match. 242 4. Change some contacts on the PSE. 243 5. Reconnect PCE to PSE to perform transfer. 244 6. Verify that new contacts match. 245 246 Returns: 247 Pass if True 248 Fail if False 249 """ 250 bt_contacts_utils.generate_contact_list(self.contacts_destination_path, 251 PSE_CONTACTS_FILE, 100) 252 phone_numbers_added = bt_contacts_utils.import_device_contacts_from_vcf( 253 self.pse, self.contacts_destination_path, PSE_CONTACTS_FILE) 254 if not self.connect_and_verify(phone_numbers_added): 255 return False 256 257 bt_contacts_utils.erase_contacts(self.pse) 258 bt_contacts_utils.generate_contact_list(self.contacts_destination_path, 259 PSE_CONTACTS_FILE, 110, 2) 260 phone_numbers_added = bt_contacts_utils.import_device_contacts_from_vcf( 261 self.pse, self.contacts_destination_path, PSE_CONTACTS_FILE) 262 return self.connect_and_verify(phone_numbers_added) 263 264 @test_tracker_info(uuid='bbe31bf5-51e8-4175-b266-1c7750e44f5b') 265 @BluetoothBaseTest.bt_test_wrap 266 def test_special_contacts(self): 267 """Test Special Contacts 268 269 Test numerous special cases of contacts that could cause errors. 270 271 Precondition: 272 1. Devices are paired. 273 274 Steps: 275 1. Add a predefined list of contacts to PSE that includes special cases: 276 2. Connect PCE to PSE to perform transfer. 277 3. Verify that contacts match. 278 279 Returns: 280 Pass if True 281 Fail if False 282 """ 283 284 vcards = [] 285 286 # Generate a contact with no email address 287 current_contact = bt_contacts_utils.VCard() 288 current_contact.first_name = "Mr." 289 current_contact.last_name = "Smiley" 290 current_contact.add_phone_number( 291 bt_contacts_utils.generate_random_phone_number()) 292 vcards.append(current_contact) 293 294 # Generate a 2nd contact with the same name but different phone number 295 current_contact = bt_contacts_utils.VCard() 296 current_contact.first_name = "Mr." 297 current_contact.last_name = "Smiley" 298 current_contact.add_phone_number( 299 bt_contacts_utils.generate_random_phone_number()) 300 vcards.append(current_contact) 301 302 # Generate a contact with no name 303 current_contact = bt_contacts_utils.VCard() 304 current_contact.email = "{}@gmail.com".format( 305 bt_contacts_utils.generate_random_string()) 306 current_contact.add_phone_number( 307 bt_contacts_utils.generate_random_phone_number()) 308 vcards.append(current_contact) 309 310 # Generate a contact with random characters in its name 311 current_contact = bt_contacts_utils.VCard() 312 current_contact.first_name = bt_contacts_utils.generate_random_string() 313 current_contact.last_name = bt_contacts_utils.generate_random_string() 314 current_contact.add_phone_number( 315 bt_contacts_utils.generate_random_phone_number()) 316 vcards.append(current_contact) 317 318 # Generate a contact with only a phone number 319 current_contact = bt_contacts_utils.VCard() 320 current_contact.add_phone_number( 321 bt_contacts_utils.generate_random_phone_number()) 322 vcards.append(current_contact) 323 324 # Generate a 2nd contact with only a phone number 325 current_contact = bt_contacts_utils.VCard() 326 current_contact.add_phone_number( 327 bt_contacts_utils.generate_random_phone_number()) 328 vcards.append(current_contact) 329 330 bt_contacts_utils.create_new_contacts_vcf_from_vcards( 331 self.contacts_destination_path, PSE_CONTACTS_FILE, vcards) 332 333 phone_numbers_added = bt_contacts_utils.import_device_contacts_from_vcf( 334 self.pse, self.contacts_destination_path, PSE_CONTACTS_FILE) 335 return self.connect_and_verify(phone_numbers_added) 336 337 @test_tracker_info(uuid='2aa2bd00-86cc-4f39-a06a-90b17ea5b320') 338 @BluetoothBaseTest.bt_test_wrap 339 def test_call_log(self): 340 """Test Call Log 341 342 Test that Call Logs are transfered 343 344 Precondition: 345 1. Devices are paired. 346 347 Steps: 348 1. Add a predefined list of calls to the PSE call log. 349 2. Connect PCE to PSE to allow call log transfer 350 3. Verify the Missed, Incoming, and Outgoing Call History 351 352 Returns: 353 Pass if True 354 Fail if False 355 """ 356 bt_contacts_utils.add_call_log( 357 self.pse, bt_contacts_utils.INCOMMING_CALL_TYPE, 358 bt_contacts_utils.generate_random_phone_number().phone_number, 359 int(time.time() * 1000)) 360 bt_contacts_utils.add_call_log( 361 self.pse, bt_contacts_utils.INCOMMING_CALL_TYPE, 362 bt_contacts_utils.generate_random_phone_number().phone_number, 363 int(time.time()) * 1000 - 4 * CALL_LOG_TIME_OFFSET_IN_MSEC) 364 bt_contacts_utils.add_call_log( 365 self.pse, bt_contacts_utils.OUTGOING_CALL_TYPE, 366 bt_contacts_utils.generate_random_phone_number().phone_number, 367 int(time.time()) * 1000 - CALL_LOG_TIME_OFFSET_IN_MSEC) 368 bt_contacts_utils.add_call_log( 369 self.pse, bt_contacts_utils.MISSED_CALL_TYPE, 370 bt_contacts_utils.generate_random_phone_number().phone_number, 371 int(time.time()) * 1000 - 2 * CALL_LOG_TIME_OFFSET_IN_MSEC) 372 bt_contacts_utils.add_call_log( 373 self.pse, bt_contacts_utils.MISSED_CALL_TYPE, 374 bt_contacts_utils.generate_random_phone_number().phone_number, 375 int(time.time()) * 1000 - 2 * CALL_LOG_TIME_OFFSET_IN_MSEC) 376 377 self.pce.droid.bluetoothPbapClientDisconnect( 378 self.pse.droid.bluetoothGetLocalAddress()) 379 self.pce.droid.bluetoothPbapClientDisconnect( 380 self.pse2.droid.bluetoothGetLocalAddress()) 381 382 bt_test_utils.connect_pri_to_sec( 383 self.pce, self.pse, 384 set([BtEnum.BluetoothProfile.PBAP_CLIENT.value])) 385 pse_call_log_count = self.pse.droid.callLogGetCount() 386 self.log.info("Waiting for {} call logs to be transfered".format( 387 pse_call_log_count)) 388 bt_contacts_utils.wait_for_call_log_update_complete(self.pce, 389 pse_call_log_count) 390 391 if not bt_contacts_utils.get_and_compare_call_logs( 392 self.pse, self.pce, bt_contacts_utils.INCOMMING_CALL_TYPE): 393 return False 394 if not bt_contacts_utils.get_and_compare_call_logs( 395 self.pse, self.pce, bt_contacts_utils.OUTGOING_CALL_TYPE): 396 return False 397 if not bt_contacts_utils.get_and_compare_call_logs( 398 self.pse, self.pce, bt_contacts_utils.MISSED_CALL_TYPE): 399 return False 400 401 return True 402 403 @test_tracker_info(uuid='bb018bf4-5a61-478d-acce-eef88050e489') 404 @BluetoothBaseTest.bt_test_wrap 405 def test_multiple_phones(self): 406 """Test Multiple Phones 407 408 Test that connects two phones and confirms contacts are transfered 409 and merged while still being associated with their original phone. 410 411 Precondition: 412 1. Devices are paired. 413 414 Steps: 415 1. Add a unique list of contacts to PSE on each phone. 416 2. Connect PCE to PSE 1 to perform transfer. 417 3. Verify contacts match. 418 4. Connect PCE to PSE 2 to perform transfer. 419 5. Verify that the PCE has a union set of contacts from 420 PSE 1 and PSE 2. 421 6. Disconnect PCE from PSE 1 to clean up contacts. 422 7. Verify that only PSE 2 contacts remain on PCE and they match. 423 8. Disconnect PCE from PSE 2 to clean up contacts. 424 425 Returns: 426 Pass if True 427 Fail if False 428 """ 429 PSE1_CONTACTS_FILE = "{}{}".format(PSE_CONTACTS_FILE, "1") 430 PSE2_CONTACTS_FILE = "{}{}".format(PSE_CONTACTS_FILE, "2") 431 432 bt_contacts_utils.generate_contact_list(self.contacts_destination_path, 433 PSE1_CONTACTS_FILE, 100) 434 bt_contacts_utils.import_device_contacts_from_vcf(self.pse, 435 self.contacts_destination_path, 436 PSE1_CONTACTS_FILE) 437 bt_contacts_utils.generate_contact_list(self.contacts_destination_path, 438 PSE2_CONTACTS_FILE, 100) 439 bt_contacts_utils.import_device_contacts_from_vcf(self.pse2, 440 self.contacts_destination_path, 441 PSE2_CONTACTS_FILE) 442 443 self.pce.droid.bluetoothPbapClientDisconnect( 444 self.pse.droid.bluetoothGetLocalAddress()) 445 self.pce.droid.bluetoothPbapClientDisconnect( 446 self.pse2.droid.bluetoothGetLocalAddress()) 447 448 bt_test_utils.connect_pri_to_sec( 449 self.pce, self.pse, 450 set([BtEnum.BluetoothProfile.PBAP_CLIENT.value])) 451 bt_contacts_utils.wait_for_phone_number_update_complete(self.pce, 100) 452 bt_contacts_utils.export_device_contacts_to_vcf( 453 self.pce, self.contacts_destination_path, PCE_CONTACTS_FILE) 454 pse1_matches = bt_contacts_utils.count_contacts_with_differences( 455 self.contacts_destination_path, PCE_CONTACTS_FILE, 456 PSE1_CONTACTS_FILE) == 0 457 458 bt_test_utils.connect_pri_to_sec( 459 self.pce, self.pse2, 460 set([BtEnum.BluetoothProfile.PBAP_CLIENT.value])) 461 bt_contacts_utils.wait_for_phone_number_update_complete(self.pce, 200) 462 bt_contacts_utils.export_device_contacts_to_vcf( 463 self.pce, self.contacts_destination_path, PCE_CONTACTS_FILE) 464 465 merged_file = open('{}{}'.format(self.contacts_destination_path, 466 MERGED_CONTACTS_FILE), 'w') 467 for contacts_file in [PSE1_CONTACTS_FILE, PSE2_CONTACTS_FILE]: 468 infile = open(self.contacts_destination_path + contacts_file) 469 merged_file.write(infile.read()) 470 471 self.log.info("Checking combined phonebook.") 472 pse1andpse2_matches = bt_contacts_utils.count_contacts_with_differences( 473 self.contacts_destination_path, PCE_CONTACTS_FILE, 474 MERGED_CONTACTS_FILE) == 0 475 476 self.pce.droid.bluetoothPbapClientDisconnect( 477 self.pse.droid.bluetoothGetLocalAddress()) 478 bt_contacts_utils.wait_for_phone_number_update_complete(self.pce, 100) 479 480 self.log.info("Checking phonebook after disconnecting first device.") 481 bt_contacts_utils.export_device_contacts_to_vcf( 482 self.pce, self.contacts_destination_path, PCE_CONTACTS_FILE) 483 pse2_matches = bt_contacts_utils.count_contacts_with_differences( 484 self.contacts_destination_path, PCE_CONTACTS_FILE, 485 PSE2_CONTACTS_FILE) == 0 486 487 bt_contacts_utils.erase_contacts(self.pse) 488 bt_contacts_utils.erase_contacts(self.pse2) 489 return pse1_matches and pse2_matches and pse1andpse2_matches