1#!/usr/bin/env python3
2#
3#   Copyright 2016 - Google
4#
5#   Licensed under the Apache License, Version 2.0 (the "License");
6#   you may not use this file except in compliance with the License.
7#   You may obtain a copy of 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,
13#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14#   See the License for the specific language governing permissions and
15#   limitations under the License.
16
17# Defines utilities that can be used for making calls indenpendent of
18# subscription IDs. This can be useful when making calls over mediums not SIM
19# based.
20
21# Make a phone call to the specified URI. It is assumed that we are making the
22# call to the user selected default account.
23#
24# We usually want to make sure that the call has ended up in a good state.
25#
26# NOTE: This util is applicable to only non-conference type calls. It is best
27# suited to test cases where only one call is in action at any point of time.
28
29import queue
30import time
31
32from acts import logger
33from acts.test_utils.tel import tel_defines
34
35def dial_number(log, ad, uri):
36    """Dial a number
37
38    Args:
39        log: log object
40        ad: android device object
41        uri: Tel number to dial
42
43    Returns:
44        True if success, False if fail.
45    """
46    log.info("Dialing up droid {} call uri {}".format(
47        ad.serial, uri))
48
49    # First check that we are not in call.
50    if ad.droid.telecomIsInCall():
51        log.info("We're still in call {}".format(ad.serial))
52        return False
53
54    # Start tracking updates.
55    ad.droid.telecomStartListeningForCallAdded()
56    #If a phone number is passed in
57    if "tel:" not in uri:
58        uri = "tel:" + uri
59    ad.droid.telecomCallTelUri(uri)
60
61    event = None
62    try:
63        event = ad.ed.pop_event(
64            tel_defines.EventTelecomCallAdded,
65            tel_defines.MAX_WAIT_TIME_CONNECTION_STATE_UPDATE)
66    except queue.Empty:
67        log.info(
68            "Did not get {} event!".format(tel_defines.EventTelecomCallAdded))
69        # Return failure.
70        return False
71    finally:
72        ad.droid.telecomStopListeningForCallAdded()
73
74    call_id = event['data']['CallId']
75    log.info("Call ID: {} dev {}".format(call_id, ad.serial))
76
77    if not call_id:
78        log.info("CallId is empty!")
79        return False
80    if not wait_for_dialing(log, ad):
81        return False
82
83    return call_id
84
85def wait_for_call_state(log, ad, call_id, state):
86    """Wait for the given call id to transition to the given call state.
87
88    Args:
89        log: log object
90        ad: android device object
91        call_id: ID of the call that we're waiting for the call state to
92        transition into.
93        state: desired final state.
94
95    Returns:
96        True if success, False if fail.
97    """
98    # Lets track the call now.
99    # NOTE: Disable this everywhere we return.
100    ad.droid.telecomCallStartListeningForEvent(
101        call_id, tel_defines.EVENT_CALL_STATE_CHANGED)
102
103    # We may have missed the update so do a quick check.
104    if ad.droid.telecomCallGetCallState(call_id) == state:
105        log.info("Call ID {} already in {} dev {}!".format(
106            call_id, state, ad.serial))
107        ad.droid.telecomCallStopListeningForEvent(call_id,
108            tel_defines.EventTelecomCallStateChanged)
109        return True
110
111    # If not then we need to poll for the event.
112    # We return if we have found a match or we timeout for further updates of
113    # the call
114    end_time = time.time() + 10
115    while True and time.time() < end_time:
116        try:
117            event = ad.ed.pop_event(
118                tel_defines.EventTelecomCallStateChanged,
119                tel_defines.MAX_WAIT_TIME_CONNECTION_STATE_UPDATE)
120            call_state = event['data']['Event']
121            if call_state == state:
122                ad.droid.telecomCallStopListeningForEvent(call_id,
123                    tel_defines.EventTelecomCallStateChanged)
124                return True
125            else:
126                log.info("Droid {} in call {} state {}".format(
127                    ad.serial, call_id, call_state))
128                continue
129        except queue.Empty:
130            log.info("Did not get into state {} dev {}".format(
131                state, ad.serial))
132            ad.droid.telecomCallStopListeningForEvent(call_id,
133                tel_defines.EventTelecomCallStateChanged)
134            return False
135    return False
136
137def hangup_call(log, ad, call_id):
138    """Hangup a number
139
140    Args:
141        log: log object
142        ad: android device object
143        call_id: Call to hangup.
144
145    Returns:
146        True if success, False if fail.
147    """
148    log.info("Hanging up droid {} call {}".format(
149        ad.serial, call_id))
150    # First check that we are in call, otherwise fail.
151    if not ad.droid.telecomIsInCall():
152        log.info("We are not in-call {}".format(ad.serial))
153        return False
154
155    # Make sure we are registered with the events.
156    ad.droid.telecomStartListeningForCallRemoved()
157
158    # Disconnect call.
159    ad.droid.telecomCallDisconnect(call_id)
160
161    # Wait for removed event.
162    event = None
163    try:
164        event = ad.ed.pop_event(
165            tel_defines.EventTelecomCallRemoved,
166            tel_defines.MAX_WAIT_TIME_CONNECTION_STATE_UPDATE)
167    except queue.Empty:
168        log.info("Did not get TelecomCallRemoved event")
169        return False
170    finally:
171        ad.droid.telecomStopListeningForCallRemoved()
172
173    log.info("Removed call {}".format(event))
174    if event['data']['CallId'] != call_id:
175        return False
176
177    return True
178
179def hangup_conf(log, ad, conf_id, timeout=10):
180    """Hangup a conference call
181
182    Args:
183        log: log object
184        ad: android device object
185        conf_id: Conf call to hangup.
186
187    Returns:
188        True if success, False if fail.
189    """
190    log.info("hangup_conf: Hanging up droid {} call {}".format(
191        ad.serial, conf_id))
192
193    # First check that we are in call, otherwise fail.
194    if not ad.droid.telecomIsInCall():
195        log.info("We are not in-call {}".format(ad.serial))
196        return False
197
198    # Disconnect call.
199    ad.droid.telecomCallDisconnect(conf_id)
200
201    start_time = time.time()
202    while time.time() < start_time + timeout:
203        call_ids = get_calls_in_states(log, ad, [tel_defines.CALL_STATE_ACTIVE])
204        log.debug("Active calls {}".format(call_ids))
205        if not call_ids:
206            return True
207        time.sleep(1)
208    log.error("Failed to hang up all conference participants")
209    return False
210
211def accept_call(log, ad, call_id):
212    """Accept a number
213
214    Args:
215        log: log object
216        ad: android device object
217        call_id: Call to accept.
218
219    Returns:
220        True if success, False if fail.
221    """
222    log.info("Accepting call at droid {} call {}".format(
223        ad.serial, call_id))
224    # First check we are in call, otherwise fail.
225    if not ad.droid.telecomIsInCall():
226        log.info("We are not in-call {}".format(ad.serial))
227        return False
228
229    # Accept the call and wait for the call to be accepted on AG.
230    ad.droid.telecomCallAnswer(call_id, tel_defines.VT_STATE_AUDIO_ONLY)
231    if not wait_for_call_state(log, ad, call_id, tel_defines.CALL_STATE_ACTIVE):
232        log.error("Call {} on droid {} not active".format(
233            call_id, ad.serial))
234        return False
235
236    return True
237
238def wait_for_not_in_call(log, ad):
239    """Wait for the droid to be OUT OF CALLING state.
240
241    Args:
242        log: log object
243        ad: android device object
244
245    Returns:
246        True if success, False if fail.
247    """
248    ad.droid.telecomStartListeningForCallRemoved()
249
250    calls = ad.droid.telecomCallGetCallIds()
251    if len(calls) > 1:
252        log.info("More than one call {} {}".format(calls, ad.serial))
253        return False
254
255    if len(calls) > 0:
256        log.info("Got calls {} for {}".format(
257            calls, ad.serial))
258        try:
259            event = ad.ed.pop_event(
260                tel_defines.EventTelecomCallRemoved,
261                tel_defines.MAX_WAIT_TIME_CONNECTION_STATE_UPDATE)
262        except queue.Empty:
263            log.info("wait_for_not_in_call Did not get {} droid {}".format(
264                tel_defines.EventTelecomCallRemoved,
265                ad.serial))
266            return False
267        finally:
268            ad.droid.telecomStopListeningForCallRemoved()
269
270    # Either we removed the only call or we never had a call previously, either
271    # ways simply check if we are in in call now.
272    return (not ad.droid.telecomIsInCall())
273
274def wait_for_dialing(log, ad):
275    """Wait for the droid to be in dialing state.
276
277    Args:
278        log: log object
279        ad: android device object
280
281    Returns:
282        True if success, False if fail.
283    """
284    # Start listening for events before anything else happens.
285    ad.droid.telecomStartListeningForCallAdded()
286
287    # First check if we re in call, then simply return.
288    if ad.droid.telecomIsInCall():
289        ad.droid.telecomStopListeningForCallAdded()
290        return True
291
292    call_id = None
293    # Now check if we already have calls matching the state.
294    calls_in_state = get_calls_in_states(log, ad,
295                                         [tel_defines.CALL_STATE_CONNECTING,
296                                         tel_defines.CALL_STATE_DIALING])
297
298    # If not then we need to poll for the calls themselves.
299    if len(calls_in_state) == 0:
300        event = None
301        try:
302            event = ad.ed.pop_event(
303                tel_defines.EventTelecomCallAdded,
304                tel_defines.MAX_WAIT_TIME_CONNECTION_STATE_UPDATE)
305        except queue.Empty:
306            log.info("Did not get {}".format(
307                tel_defines.EventTelecomCallAdded))
308            return False
309        finally:
310            ad.droid.telecomStopListeningForCallAdded()
311        call_id = event['data']['CallId']
312    else:
313        call_id = calls_in_state[0]
314
315    # We may still not be in-call if the call setup is going on.
316    # We wait for the call state to move to dialing.
317    log.info("call id {} droid {}".format(call_id, ad.serial))
318    if not wait_for_call_state(
319        log, ad, call_id, tel_defines.CALL_STATE_DIALING):
320        return False
321
322    # Finally check the call state.
323    return ad.droid.telecomIsInCall()
324
325def wait_for_ringing(log, ad):
326    """Wait for the droid to be in ringing state.
327
328    Args:
329        log: log object
330        ad: android device object
331
332    Returns:
333        True if success, False if fail.
334    """
335    log.info("waiting for ringing {}".format(ad.serial))
336    # Start listening for events before anything else happens.
337    ad.droid.telecomStartListeningForCallAdded()
338
339    # First check if we re in call, then simply return.
340    if ad.droid.telecomIsInCall():
341        log.info("Device already in call {}".format(ad.serial))
342        ad.droid.telecomStopListeningForCallAdded()
343        return True
344
345    call_id = None
346    # Now check if we already have calls matching the state.
347    calls_in_state = ad.droid.telecomCallGetCallIds()
348
349    for c_id in calls_in_state:
350        if ad.droid.telecomCallGetCallState(c_id) == tel_defines.CALL_STATE_RINGING:
351            return True
352
353    event = None
354    call_id = None
355    try:
356        event = ad.ed.pop_event(
357            tel_defines.EventTelecomCallAdded,
358            tel_defines.MAX_WAIT_TIME_CALLEE_RINGING)
359    except queue.Empty:
360        log.info("Did not get {} droid {}".format(
361            tel_defines.EventTelecomCallAdded,
362            ad.serial))
363        return False
364    finally:
365        ad.droid.telecomStopListeningForCallAdded()
366    call_id = event['data']['CallId']
367    log.info("wait_for_ringing call found {} dev {}".format(
368        call_id, ad.serial))
369
370    # If the call already existed then we would have returned above otherwise
371    # we will verify that the newly added call is indeed ringing.
372    if not wait_for_call_state(
373        log, ad, call_id, tel_defines.CALL_STATE_RINGING):
374        log.info("No ringing call id {} droid {}".format(
375            call_id, ad.serial))
376        return False
377    return True
378
379def wait_for_active(log, ad):
380    """Wait for the droid to be in active call state.
381
382    Args:
383        log: log object
384        ad: android device object
385
386    Returns:
387        True if success, False if fail.
388    """
389    log.info("waiting for active {}".format(ad.serial))
390    # Start listening for events before anything else happens.
391    ad.droid.telecomStartListeningForCallAdded()
392
393    call_id = None
394    # Now check if we already have calls matching the state.
395    calls_in_state = ad.droid.telecomCallGetCallIds()
396
397    if len(calls_in_state) == 0:
398        event = None
399        try:
400            event = ad.ed.pop_event(
401                tel_defines.EventTelecomCallAdded,
402                tel_defines.MAX_WAIT_TIME_CALLEE_RINGING)
403        except queue.Empty:
404            log.info("Did not get {} droid {}".format(
405                tel_defines.EventTelecomCallAdded,
406                ad.serial))
407            return False
408        finally:
409            ad.droid.telecomStopListeningForCallAdded()
410        call_id = event['data']['CallId']
411        log.info("wait_for_ringing call found {} dev {}".format(
412            call_id, ad.serial))
413    else:
414        call_id = calls_in_state[0]
415
416    # We have found a new call to be added now wait it to transition into
417    # active state.
418    if not wait_for_call_state(
419        log, ad, call_id, tel_defines.CALL_STATE_ACTIVE):
420        log.info("No active call id {} droid {}".format(
421            call_id, ad.serial))
422        return False
423    return True
424
425def wait_for_conference(log, ad, participants=2, timeout=10):
426    """Wait for the droid to be in a conference with calls specified
427    in conf_calls.
428
429    Args:
430        log: log object
431        ad: android device object
432        participants: conference participant count
433
434    Returns:
435        call_id if success, None if fail.
436    """
437
438    def get_conference_id(callers):
439        for call_id in callers:
440            call_details = ad.droid.telecomCallGetCallById(call_id).get("Details")
441            if set([tel_defines.CALL_PROPERTY_CONFERENCE]).issubset(call_details.get("Properties")):
442                return call_id
443        return None
444
445    log.info("waiting for conference {}".format(ad.serial))
446    start_time = time.time()
447    while time.time() < start_time + timeout:
448        participant_callers = get_calls_in_states(log, ad, [tel_defines.CALL_STATE_ACTIVE])
449        if (len(participant_callers) == participants + 1):
450            return get_conference_id(participant_callers)
451        time.sleep(1)
452    return None
453
454def get_call_id_children(log, ad, call_id):
455    """Return the list of calls that are children to call_id
456
457    Args:
458        log: log object
459        ad: android device object
460        call_id: Conference call id
461
462    Returns:
463        List containing call_ids.
464    """
465    call = ad.droid.telecomCallGetCallById(call_id)
466    call_chld = set(call['Children'])
467    log.info("get_call_id_children droid {} call {} children {}".format(
468        ad.serial, call, call_chld))
469    return call_chld
470
471def get_calls_in_states(log, ad, call_states):
472    """Return the list of calls that are any of the states passed in call_states
473
474    Args:
475        log: log object
476        ad: android device object
477        call_states: List of desired call states
478
479    Returns:
480        List containing call_ids.
481    """
482    # Get the list of calls.
483    call_ids = ad.droid.telecomCallGetCallIds()
484    call_in_state = []
485    for call_id in call_ids:
486        call = ad.droid.telecomCallGetCallById(call_id)
487        log.info("Call id: {} desc: {}".format(call_id, call))
488        if call['State'] in call_states:
489            log.info("Adding call id {} to result set.".format(call_id))
490            call_in_state.append(call_id)
491    return call_in_state
492