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 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 setup_class(self): 44 if not super(BtCarPbapTest, self).setup_class(): 45 return False 46 self.pce = self.android_devices[0] 47 self.pse = self.android_devices[1] 48 self.pse2 = self.android_devices[2] 49 self.contacts_destination_path = self.log_path + "/" 50 51 permissions_list = [ 52 "android.permission.READ_CONTACTS", 53 "android.permission.WRITE_CONTACTS", 54 "android.permission.READ_EXTERNAL_STORAGE" 55 ] 56 for device in self.android_devices: 57 for permission in permissions_list: 58 device.adb.shell( 59 "pm grant com.google.android.contacts {}".format(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(bt_contacts_utils.erase_contacts(device) for device in self.android_devices) 117 finally: 118 # Allow all content providers to synchronize. 119 time.sleep(1) 120 121 def verify_contacts_match(self): 122 bt_contacts_utils.export_device_contacts_to_vcf( 123 self.pce, self.contacts_destination_path, PCE_CONTACTS_FILE) 124 return bt_contacts_utils.count_contacts_with_differences( 125 self.contacts_destination_path, PCE_CONTACTS_FILE, 126 PSE_CONTACTS_FILE) == 0 127 128 def connect_and_verify(self, count): 129 bt_test_utils.connect_pri_to_sec( 130 self.pce, self.pse, 131 set([BtEnum.BluetoothProfile.PBAP_CLIENT.value])) 132 bt_contacts_utils.wait_for_phone_number_update_complete(self.pce, 133 count) 134 contacts_added = self.verify_contacts_match() 135 self.pce.droid.bluetoothPbapClientDisconnect( 136 self.pse.droid.bluetoothGetLocalAddress()) 137 contacts_removed = bt_contacts_utils.wait_for_phone_number_update_complete( 138 self.pce, 0) 139 return contacts_added and contacts_removed 140 141 @test_tracker_info(uuid='7dcdecfc-42d1-4f41-b66e-823c8f161356') 142 @BluetoothBaseTest.bt_test_wrap 143 def test_pbap_connect_and_disconnect(self): 144 """Test Connectivity 145 146 Test connecting with the server enabled and disabled 147 148 Precondition: 149 1. Devices are paired. 150 151 Steps: 152 1. Disable permission on PSE to prevent PCE from connecting 153 2. Attempt to connect PCE to PSE 154 3. Verify connection failed 155 4. Enable permission on PSE to allow PCE to connect 156 5. Attempt to connect PCE to PSE 157 6. Verify connection succeeded 158 159 Returns: 160 Pass if True 161 Fail if False 162 """ 163 self.pse.droid.bluetoothChangeProfileAccessPermission( 164 self.pce.droid.bluetoothGetLocalAddress(), 165 BtEnum.BluetoothProfile.PBAP_SERVER.value, 166 BtEnum.BluetoothAccessLevel.ACCESS_DENIED.value) 167 if bt_test_utils.connect_pri_to_sec( 168 self.pce, self.pse, 169 set([BtEnum.BluetoothProfile.PBAP_CLIENT.value])): 170 self.log.error("Client connected and shouldn't be.") 171 return False 172 173 self.pce.droid.bluetoothPbapClientDisconnect( 174 self.pse.droid.bluetoothGetLocalAddress()) 175 176 self.pse.droid.bluetoothChangeProfileAccessPermission( 177 self.pce.droid.bluetoothGetLocalAddress(), 178 BtEnum.BluetoothProfile.PBAP_SERVER.value, 179 BtEnum.BluetoothAccessLevel.ACCESS_ALLOWED.value) 180 181 if not bt_test_utils.connect_pri_to_sec( 182 self.pce, self.pse, 183 set([BtEnum.BluetoothProfile.PBAP_CLIENT.value])): 184 self.log.error("No client connected and should be.") 185 return False 186 187 return True 188 189 @test_tracker_info(uuid='1733efb9-71af-4956-bd3a-0d3167d94d0c') 190 @BluetoothBaseTest.bt_test_wrap 191 def test_contact_download(self): 192 """Test Contact Download 193 194 Test download of contacts from a clean state. 195 196 Precondition: 197 1. Devices are paired. 198 199 Steps: 200 1. Erase contacts from PSE and PCE. 201 2. Add a predefined list of contacts to PSE. 202 3. Connect PCE to PSE to perform transfer. 203 4. Compare transfered contacts. 204 5. Disconnect. 205 6. Verify PCE cleaned up contact list. 206 207 Returns: 208 Pass if True 209 Fail if False 210 """ 211 bt_contacts_utils.generate_contact_list(self.contacts_destination_path, 212 PSE_CONTACTS_FILE, 100) 213 phone_numbers_added = bt_contacts_utils.import_device_contacts_from_vcf( 214 self.pse, self.contacts_destination_path, PSE_CONTACTS_FILE) 215 bt_test_utils.connect_pri_to_sec( 216 self.pce, self.pse, 217 set([BtEnum.BluetoothProfile.PBAP_CLIENT.value])) 218 bt_contacts_utils.wait_for_phone_number_update_complete( 219 self.pce, phone_numbers_added) 220 if not self.verify_contacts_match(): 221 return False 222 return bt_contacts_utils.erase_contacts(self.pce) 223 224 @test_tracker_info(uuid='99dc6ac6-b7cf-45ce-927b-8c4ebf8ab664') 225 @BluetoothBaseTest.bt_test_wrap 226 def test_modify_phonebook(self): 227 """Test Modify Phonebook 228 229 Test changing contacts and reconnecting PBAP. 230 231 Precondition: 232 1. Devices are paired. 233 234 Steps: 235 1. Add a predefined list of contacts to PSE. 236 2. Connect PCE to PSE to perform transfer. 237 3. Verify that contacts match. 238 4. Change some contacts on the PSE. 239 5. Reconnect PCE to PSE to perform transfer. 240 6. Verify that new contacts match. 241 242 Returns: 243 Pass if True 244 Fail if False 245 """ 246 bt_contacts_utils.generate_contact_list(self.contacts_destination_path, 247 PSE_CONTACTS_FILE, 100) 248 phone_numbers_added = bt_contacts_utils.import_device_contacts_from_vcf( 249 self.pse, self.contacts_destination_path, PSE_CONTACTS_FILE) 250 if not self.connect_and_verify(phone_numbers_added): 251 return False 252 253 bt_contacts_utils.erase_contacts(self.pse) 254 bt_contacts_utils.generate_contact_list(self.contacts_destination_path, 255 PSE_CONTACTS_FILE, 110, 2) 256 phone_numbers_added = bt_contacts_utils.import_device_contacts_from_vcf( 257 self.pse, self.contacts_destination_path, PSE_CONTACTS_FILE) 258 return self.connect_and_verify(phone_numbers_added) 259 260 @test_tracker_info(uuid='bbe31bf5-51e8-4175-b266-1c7750e44f5b') 261 @BluetoothBaseTest.bt_test_wrap 262 def test_special_contacts(self): 263 """Test Special Contacts 264 265 Test numerous special cases of contacts that could cause errors. 266 267 Precondition: 268 1. Devices are paired. 269 270 Steps: 271 1. Add a predefined list of contacts to PSE that includes special cases: 272 2. Connect PCE to PSE to perform transfer. 273 3. Verify that contacts match. 274 275 Returns: 276 Pass if True 277 Fail if False 278 """ 279 280 vcards = [] 281 282 # Generate a contact with no email address 283 current_contact = bt_contacts_utils.VCard() 284 current_contact.first_name = "Mr." 285 current_contact.last_name = "Smiley" 286 current_contact.add_phone_number( 287 bt_contacts_utils.generate_random_phone_number()) 288 vcards.append(current_contact) 289 290 # Generate a 2nd contact with the same name but different phone number 291 current_contact = bt_contacts_utils.VCard() 292 current_contact.first_name = "Mr." 293 current_contact.last_name = "Smiley" 294 current_contact.add_phone_number( 295 bt_contacts_utils.generate_random_phone_number()) 296 vcards.append(current_contact) 297 298 # Generate a contact with no name 299 current_contact = bt_contacts_utils.VCard() 300 current_contact.email = "{}@gmail.com".format( 301 bt_contacts_utils.generate_random_string()) 302 current_contact.add_phone_number( 303 bt_contacts_utils.generate_random_phone_number()) 304 vcards.append(current_contact) 305 306 # Generate a contact with random characters in its name 307 current_contact = bt_contacts_utils.VCard() 308 current_contact.first_name = bt_contacts_utils.generate_random_string() 309 current_contact.last_name = bt_contacts_utils.generate_random_string() 310 current_contact.add_phone_number( 311 bt_contacts_utils.generate_random_phone_number()) 312 vcards.append(current_contact) 313 314 # Generate a contact with only a phone number 315 current_contact = bt_contacts_utils.VCard() 316 current_contact.add_phone_number( 317 bt_contacts_utils.generate_random_phone_number()) 318 vcards.append(current_contact) 319 320 # Generate a 2nd contact with only a phone number 321 current_contact = bt_contacts_utils.VCard() 322 current_contact.add_phone_number( 323 bt_contacts_utils.generate_random_phone_number()) 324 vcards.append(current_contact) 325 326 bt_contacts_utils.create_new_contacts_vcf_from_vcards( 327 self.contacts_destination_path, PSE_CONTACTS_FILE, vcards) 328 329 phone_numbers_added = bt_contacts_utils.import_device_contacts_from_vcf( 330 self.pse, self.contacts_destination_path, PSE_CONTACTS_FILE) 331 return self.connect_and_verify(phone_numbers_added) 332 333 @test_tracker_info(uuid='2aa2bd00-86cc-4f39-a06a-90b17ea5b320') 334 @BluetoothBaseTest.bt_test_wrap 335 def test_call_log(self): 336 """Test Call Log 337 338 Test that Call Logs are transfered 339 340 Precondition: 341 1. Devices are paired. 342 343 Steps: 344 1. Add a predefined list of calls to the PSE call log. 345 2. Connect PCE to PSE to allow call log transfer 346 3. Verify the Missed, Incoming, and Outgoing Call History 347 348 Returns: 349 Pass if True 350 Fail if False 351 """ 352 bt_contacts_utils.add_call_log( 353 self.pse, bt_contacts_utils.INCOMMING_CALL_TYPE, 354 bt_contacts_utils.generate_random_phone_number().phone_number, 355 int(time.time() * 1000)) 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 - 4 * CALL_LOG_TIME_OFFSET_IN_MSEC) 360 bt_contacts_utils.add_call_log( 361 self.pse, bt_contacts_utils.OUTGOING_CALL_TYPE, 362 bt_contacts_utils.generate_random_phone_number().phone_number, 363 int(time.time()) * 1000 - CALL_LOG_TIME_OFFSET_IN_MSEC) 364 bt_contacts_utils.add_call_log( 365 self.pse, bt_contacts_utils.MISSED_CALL_TYPE, 366 bt_contacts_utils.generate_random_phone_number().phone_number, 367 int(time.time()) * 1000 - 2 * 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 373 self.pce.droid.bluetoothPbapClientDisconnect( 374 self.pse.droid.bluetoothGetLocalAddress()) 375 self.pce.droid.bluetoothPbapClientDisconnect( 376 self.pse2.droid.bluetoothGetLocalAddress()) 377 378 bt_test_utils.connect_pri_to_sec( 379 self.pce, self.pse, 380 set([BtEnum.BluetoothProfile.PBAP_CLIENT.value])) 381 pse_call_log_count = self.pse.droid.callLogGetCount() 382 self.log.info("Waiting for {} call logs to be transfered".format( 383 pse_call_log_count)) 384 bt_contacts_utils.wait_for_call_log_update_complete(self.pce, 385 pse_call_log_count) 386 387 if not bt_contacts_utils.get_and_compare_call_logs( 388 self.pse, self.pce, bt_contacts_utils.INCOMMING_CALL_TYPE): 389 return False 390 if not bt_contacts_utils.get_and_compare_call_logs( 391 self.pse, self.pce, bt_contacts_utils.OUTGOING_CALL_TYPE): 392 return False 393 if not bt_contacts_utils.get_and_compare_call_logs( 394 self.pse, self.pce, bt_contacts_utils.MISSED_CALL_TYPE): 395 return False 396 397 return True 398 399 @test_tracker_info(uuid='bb018bf4-5a61-478d-acce-eef88050e489') 400 @BluetoothBaseTest.bt_test_wrap 401 def test_multiple_phones(self): 402 """Test Multiple Phones 403 404 Test that connects two phones and confirms contacts are transfered 405 and merged while still being associated with their original phone. 406 407 Precondition: 408 1. Devices are paired. 409 410 Steps: 411 1. Add a unique list of contacts to PSE on each phone. 412 2. Connect PCE to PSE 1 to perform transfer. 413 3. Verify contacts match. 414 4. Connect PCE to PSE 2 to perform transfer. 415 5. Verify that the PCE has a union set of contacts from 416 PSE 1 and PSE 2. 417 6. Disconnect PCE from PSE 1 to clean up contacts. 418 7. Verify that only PSE 2 contacts remain on PCE and they match. 419 8. Disconnect PCE from PSE 2 to clean up contacts. 420 421 Returns: 422 Pass if True 423 Fail if False 424 """ 425 PSE1_CONTACTS_FILE = "{}{}".format(PSE_CONTACTS_FILE, "1") 426 PSE2_CONTACTS_FILE = "{}{}".format(PSE_CONTACTS_FILE, "2") 427 428 bt_contacts_utils.generate_contact_list(self.contacts_destination_path, 429 PSE1_CONTACTS_FILE, 100) 430 bt_contacts_utils.import_device_contacts_from_vcf(self.pse, 431 self.contacts_destination_path, 432 PSE1_CONTACTS_FILE) 433 bt_contacts_utils.generate_contact_list(self.contacts_destination_path, 434 PSE2_CONTACTS_FILE, 100) 435 bt_contacts_utils.import_device_contacts_from_vcf(self.pse2, 436 self.contacts_destination_path, 437 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('{}{}'.format(self.contacts_destination_path, 462 MERGED_CONTACTS_FILE), 'w') 463 for contacts_file in [PSE1_CONTACTS_FILE, PSE2_CONTACTS_FILE]: 464 infile = open(self.contacts_destination_path + contacts_file) 465 merged_file.write(infile.read()) 466 467 self.log.info("Checking combined phonebook.") 468 pse1andpse2_matches = bt_contacts_utils.count_contacts_with_differences( 469 self.contacts_destination_path, PCE_CONTACTS_FILE, 470 MERGED_CONTACTS_FILE) == 0 471 472 self.pce.droid.bluetoothPbapClientDisconnect( 473 self.pse.droid.bluetoothGetLocalAddress()) 474 bt_contacts_utils.wait_for_phone_number_update_complete(self.pce, 100) 475 476 self.log.info("Checking phonebook after disconnecting first device.") 477 bt_contacts_utils.export_device_contacts_to_vcf( 478 self.pce, self.contacts_destination_path, PCE_CONTACTS_FILE) 479 pse2_matches = bt_contacts_utils.count_contacts_with_differences( 480 self.contacts_destination_path, PCE_CONTACTS_FILE, 481 PSE2_CONTACTS_FILE) == 0 482 483 bt_contacts_utils.erase_contacts(self.pse) 484 bt_contacts_utils.erase_contacts(self.pse2) 485 return pse1_matches and pse2_matches and pse1andpse2_matches 486 487