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