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"""
17Test the HFP profile for basic calling functionality.
18"""
19
20import time
21from acts.test_decorators import test_tracker_info
22from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
23from acts.test_utils.bt.BluetoothCarHfpBaseTest import BluetoothCarHfpBaseTest
24from acts.test_utils.bt import BtEnum
25from acts.test_utils.bt import bt_test_utils
26from acts.test_utils.car import car_telecom_utils
27from acts.test_utils.car import tel_telecom_utils
28from acts.test_utils.tel import tel_defines
29
30BLUETOOTH_PKG_NAME = "com.android.bluetooth"
31CALL_TYPE_OUTGOING = "CALL_TYPE_OUTGOING"
32CALL_TYPE_INCOMING = "CALL_TYPE_INCOMING"
33AUDIO_STATE_DISCONNECTED = 0
34AUDIO_STATE_ROUTED = 2
35SHORT_TIMEOUT = 5
36
37
38class BtCarHfpTest(BluetoothCarHfpBaseTest):
39    def setup_class(self):
40        if not super(BtCarHfpTest, self).setup_class():
41            return False
42        # Disable the A2DP profile.
43        bt_test_utils.set_profile_priority(self.hf, self.ag, [
44            BtEnum.BluetoothProfile.PBAP_CLIENT.value,
45            BtEnum.BluetoothProfile.A2DP_SINK.value
46        ], BtEnum.BluetoothPriorityLevel.PRIORITY_OFF)
47        bt_test_utils.set_profile_priority(
48            self.hf, self.ag, [BtEnum.BluetoothProfile.HEADSET_CLIENT.value],
49            BtEnum.BluetoothPriorityLevel.PRIORITY_ON)
50
51        if not bt_test_utils.connect_pri_to_sec(
52                self.hf, self.ag,
53                set([BtEnum.BluetoothProfile.HEADSET_CLIENT.value])):
54            self.log.error("Failed to connect.")
55            return False
56        return True
57
58    @test_tracker_info(uuid='4ce2195a-b70a-4584-912e-cbd20d20e19d')
59    @BluetoothBaseTest.bt_test_wrap
60    def test_default_calling_account(self):
61        """
62        Tests if the default calling account is coming from the
63        bluetooth pacakge.
64
65        Precondition:
66        1. Devices are connected.
67
68        Steps:
69        1. Check if the default calling account is via Bluetooth package.
70
71        Returns:
72          Pass if True
73          Fail if False
74
75        Priority: 0
76        """
77        selected_acc = \
78            self.hf.droid.telecomGetUserSelectedOutgoingPhoneAccount()
79        if not selected_acc:
80            self.hf.log.error("No default account found.")
81            return False
82
83        # Check if the default account is from the Bluetooth package. This is a
84        # light weight check.
85        try:
86            acc_component_id = selected_acc['ComponentName']
87        except KeyError:
88            self.hf.log.error("No component name for account {}".format(
89                selected_acc))
90            return False
91        if not acc_component_id.startswith(BLUETOOTH_PKG_NAME):
92            self.hf.log.error("Component name does not start with pkg name {}".
93                              format(selected_acc))
94            return False
95        return True
96
97    @test_tracker_info(uuid='e579009d-05f3-4236-a698-5de8c11d73a9')
98    @BluetoothBaseTest.bt_test_wrap
99    def test_outgoing_call_hf(self):
100        """
101        Tests if we can make a phone call from HF role and disconnect from HF
102        role.
103
104        Precondition:
105        1. Devices are connected.
106
107        Steps:
108        1. Make a call from HF role.
109        2. Wait for the HF, AG to be dialing and RE to see the call ringing.
110        3. Hangup the call on HF role.
111        4. Wait for all devices to hangup the call.
112
113        Returns:
114          Pass if True
115          Fail if False
116
117        Priority: 0
118        """
119        return self.dial_a_hangup_b(self.hf, self.hf)
120
121    @test_tracker_info(uuid='c9d5f9cd-f275-4adf-b212-c2e9a70d4cac')
122    @BluetoothBaseTest.bt_test_wrap
123    def test_outgoing_call_ag(self):
124        """
125        Tests if we can make a phone call from AG role and disconnect from AG
126        role.
127
128        Precondition:
129        1. Devices are connected.
130
131        Steps:
132        1. Make a call from AG role.
133        2. Wait for the HF, AG to be in dialing and RE to see the call ringing.
134        3. Hangup the call on AG role.
135        4. Wait for all devices to hangup the call.
136
137        Returns:
138          Pass if True
139          Fail if False
140
141        Priority: 0
142        """
143        return self.dial_a_hangup_b(self.ag, self.ag)
144
145    @test_tracker_info(uuid='908c199b-ca65-4694-821d-1b864ee3fe69')
146    @BluetoothBaseTest.bt_test_wrap
147    def test_outgoing_dial_ag_hangup_hf(self):
148        """
149        Tests if we can make a phone call from AG role and disconnect from HF
150        role.
151
152        Precondition:
153        1. Devices are connected.
154
155        Steps:
156        1. Make a call from AG role.
157        2. Wait for the HF, AG to show dialing and RE to see the call ringing.
158        3. Hangup the call on HF role.
159        4. Wait for all devices to hangup the call.
160
161        Returns:
162          Pass if True
163          Fail if False
164
165        Priority: 0
166        """
167        return self.dial_a_hangup_b(self.ag, self.hf)
168
169    @test_tracker_info(uuid='5d1d52c7-51d8-4c82-b437-2e91a6220db3')
170    @BluetoothBaseTest.bt_test_wrap
171    def test_outgoing_dial_hf_hangup_ag(self):
172        """
173        Tests if we can make a phone call from HF role and disconnect from AG
174        role.
175
176        Precondition:
177        1. Devices are connected.
178
179        Steps:
180        1. Make a call from HF role.
181        2. Wait for the HF, AG to show dialing and RE to see the call ringing.
182        3. Hangup the call on AG role.
183        4. Wait for all devices to hangup the call.
184
185        Returns:
186          Pass if True
187          Fail if False
188
189        Priority: 0
190        """
191        return self.dial_a_hangup_b(self.hf, self.ag)
192
193    @test_tracker_info(uuid='a718e238-7e31-40c9-a45b-72081210cc73')
194    @BluetoothBaseTest.bt_test_wrap
195    def test_incoming_dial_re_hangup_re(self):
196        """
197        Tests if we can make a phone call from remote and disconnect from
198        remote.
199
200        Precondition:
201        1. Devices are connected.
202
203        Steps:
204        1. Make a call from RE role.
205        2. Wait for the HF, AG to show ringing and RE to see the call dialing.
206        3. Hangup the call on RE role.
207        4. Wait for all devices to hangup the call.
208
209        Returns:
210          Pass if True
211          Fail if False
212
213        Priority: 0
214        """
215        return self.dial_a_hangup_b(self.re, self.re, self.ag_phone_number)
216
217    def test_bluetooth_voice_recognition_assistant(self):
218        """
219        Tests if we can initate a remote Voice Recognition session.
220
221        Precondition:
222        1. Devices are connected.
223
224        Steps:
225        1. Verify that audio isn't routed between the HF and AG.
226        2. From the HF send a BVRA command.
227        3. Verify that audio is routed from the HF to AG.
228        4. From the HF send a BVRA command to stop the session.
229        5. Verify that audio is no longer routed from the HF to AG.
230
231        Returns:
232          Pass if True
233          Fail if False
234
235        Priority: 0
236        """
237        audio_state = self.hf.droid.bluetoothHfpClientGetAudioState(
238            self.ag.droid.bluetoothGetLocalAddress())
239        if (audio_state != AUDIO_STATE_DISCONNECTED):
240            self.log.info(
241                "Audio connected before test started, current state {}.".
242                format(str(audio_state)))
243            return False
244        bvra_started = self.hf.droid.bluetoothHfpClientStartVoiceRecognition(
245            self.ag.droid.bluetoothGetLocalAddress())
246        if (bvra_started != True):
247            self.log.info("BVRA Failed to start.")
248            return False
249        time.sleep(SHORT_TIMEOUT)
250        audio_state = self.hf.droid.bluetoothHfpClientGetAudioState(
251            self.ag.droid.bluetoothGetLocalAddress())
252        if (audio_state != AUDIO_STATE_ROUTED):
253            self.log.info("Audio didn't route, current state {}.".format(
254                str(audio_state)))
255            return False
256        bvra_stopped = self.hf.droid.bluetoothHfpClientStopVoiceRecognition(
257            self.ag.droid.bluetoothGetLocalAddress())
258        if (bvra_stopped != True):
259            self.log.info("BVRA Failed to stop.")
260            return False
261        time.sleep(SHORT_TIMEOUT)
262        audio_state = self.hf.droid.bluetoothHfpClientGetAudioState(
263            self.ag.droid.bluetoothGetLocalAddress())
264        if (audio_state != AUDIO_STATE_DISCONNECTED):
265            self.log.info("Audio didn't cleanup, current state {}.".format(
266                str(audio_state)))
267            return False
268        return True
269
270    def dial_a_hangup_b(self, caller, callee, ph=""):
271        """
272        a, b and c can be either of AG, HF or Remote.
273        1. Make a call from 'a' on a fixed number.
274        2. Wait for the call to get connected (check on both 'a' and 'b')
275           Check that 'c' is in ringing state.
276        3. Hangup the call on 'b'.
277        4. Wait for call to get completely disconnected
278        (check on both 'a' and 'b')
279        It is assumed that scenarios will not go into voice mail.
280        """
281        if ph == "": ph = self.re_phone_number
282
283        # Determine if this is outgoing or incoming call.
284        call_type = None
285        if caller == self.ag or caller == self.hf:
286            call_type = CALL_TYPE_OUTGOING
287            if callee != self.ag and callee != self.hf:
288                self.log.info("outgoing call should terminate at AG or HF")
289                return False
290        elif caller == self.re:
291            call_type = CALL_TYPE_INCOMING
292            if callee != self.re:
293                self.log.info("Incoming call should terminate at Re")
294                return False
295
296        self.log.info("Call type is {}".format(call_type))
297
298        # make a call on 'caller'
299        if not tel_telecom_utils.dial_number(self.log, caller, ph):
300            return False
301
302        # Give time for state to update due to carrier limitations
303        time.sleep(SHORT_TIMEOUT)
304        # Check that everyone is in dialing/ringing state.
305        ret = True
306        if call_type == CALL_TYPE_OUTGOING:
307            ret &= tel_telecom_utils.wait_for_dialing(self.log, self.hf)
308            ret &= tel_telecom_utils.wait_for_dialing(self.log, self.ag)
309            ret &= tel_telecom_utils.wait_for_ringing(self.log, self.re)
310        else:
311            ret &= tel_telecom_utils.wait_for_ringing(self.log, self.hf)
312            ret &= tel_telecom_utils.wait_for_ringing(self.log, self.ag)
313            ret &= tel_telecom_utils.wait_for_dialing(self.log, self.re)
314        if not ret:
315            return False
316
317        # Give time for state to update due to carrier limitations
318        time.sleep(SHORT_TIMEOUT)
319        # Check if we have any calls with dialing or active state on 'b'.
320        # We assume we never disconnect from 'ringing' state since it will lead
321        # to voicemail.
322        call_state_dialing_or_active = \
323            [tel_defines.CALL_STATE_CONNECTING,
324             tel_defines.CALL_STATE_DIALING,
325             tel_defines.CALL_STATE_ACTIVE]
326
327        calls_in_dialing_or_active = tel_telecom_utils.get_calls_in_states(
328            self.log, callee, call_state_dialing_or_active)
329
330        # Make sure there is only one!
331        if len(calls_in_dialing_or_active) != 1:
332            self.log.info("Call State in dialing or active failed {}".format(
333                calls_in_dialing_or_active))
334            return False
335
336        # Hangup the *only* call on 'callee'
337        if not car_telecom_utils.hangup_call(self.log, callee,
338                                             calls_in_dialing_or_active[0]):
339            return False
340
341        time.sleep(SHORT_TIMEOUT)
342        # Make sure everyone got out of in call state.
343        for d in self.android_devices:
344            ret &= tel_telecom_utils.wait_for_not_in_call(self.log, d)
345        return ret
346