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