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