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