1#!/usr/bin/env python3.4
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):
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    # Get the list of children for this conference.
199    all_calls = get_call_id_children(log, ad, conf_id)
200
201    # All calls that needs disconnecting (Parent + Children)
202    all_calls.add(conf_id)
203
204    # Make sure we are registered with the events.
205    ad.droid.telecomStartListeningForCallRemoved()
206
207    # Disconnect call.
208    ad.droid.telecomCallDisconnect(conf_id)
209
210    # Wait for removed event.
211    while len(all_calls) > 0:
212        event = None
213        try:
214            event = ad.ed.pop_event(
215                tel_defines.EventTelecomCallRemoved,
216                tel_defines.MAX_WAIT_TIME_CONNECTION_STATE_UPDATE)
217        except queue.Empty:
218            log.info("Did not get TelecomCallRemoved event")
219            ad.droid.telecomStopListeningForCallRemoved()
220            return False
221
222        removed_call_id = event['data']['CallId']
223        all_calls.remove(removed_call_id)
224        log.info("Removed call {} left calls {}".format(removed_call_id, all_calls))
225
226    ad.droid.telecomStopListeningForCallRemoved()
227    return True
228
229def accept_call(log, ad, call_id):
230    """Accept a number
231
232    Args:
233        log: log object
234        ad: android device object
235        call_id: Call to accept.
236
237    Returns:
238        True if success, False if fail.
239    """
240    log.info("Accepting call at droid {} call {}".format(
241        ad.serial, call_id))
242    # First check we are in call, otherwise fail.
243    if not ad.droid.telecomIsInCall():
244        log.info("We are not in-call {}".format(ad.serial))
245        return False
246
247    # Accept the call and wait for the call to be accepted on AG.
248    ad.droid.telecomCallAnswer(call_id, tel_defines.VT_STATE_AUDIO_ONLY)
249    if not wait_for_call_state(log, ad, call_id, tel_defines.CALL_STATE_ACTIVE):
250        log.error("Call {} on droid {} not active".format(
251            call_id, ad.serial))
252        return False
253
254    return True
255
256def wait_for_not_in_call(log, ad):
257    """Wait for the droid to be OUT OF CALLING state.
258
259    Args:
260        log: log object
261        ad: android device object
262
263    Returns:
264        True if success, False if fail.
265    """
266    ad.droid.telecomStartListeningForCallRemoved()
267
268    calls = ad.droid.telecomCallGetCallIds()
269    if len(calls) > 1:
270        log.info("More than one call {} {}".format(calls, ad.serial))
271        return False
272
273    if len(calls) > 0:
274        log.info("Got calls {} for {}".format(
275            calls, ad.serial))
276        try:
277            event = ad.ed.pop_event(
278                tel_defines.EventTelecomCallRemoved,
279                tel_defines.MAX_WAIT_TIME_CONNECTION_STATE_UPDATE)
280        except queue.Empty:
281            log.info("wait_for_not_in_call Did not get {} droid {}".format(
282                tel_defines.EventTelecomCallRemoved,
283                ad.serial))
284            return False
285        finally:
286            ad.droid.telecomStopListeningForCallRemoved()
287
288    # Either we removed the only call or we never had a call previously, either
289    # ways simply check if we are in in call now.
290    return (not ad.droid.telecomIsInCall())
291
292def wait_for_dialing(log, ad):
293    """Wait for the droid to be in dialing state.
294
295    Args:
296        log: log object
297        ad: android device object
298
299    Returns:
300        True if success, False if fail.
301    """
302    # Start listening for events before anything else happens.
303    ad.droid.telecomStartListeningForCallAdded()
304
305    # First check if we re in call, then simply return.
306    if ad.droid.telecomIsInCall():
307        ad.droid.telecomStopListeningForCallAdded()
308        return True
309
310    call_id = None
311    # Now check if we already have calls matching the state.
312    calls_in_state = get_calls_in_states(log, ad,
313                                         [tel_defines.CALL_STATE_CONNECTING,
314                                         tel_defines.CALL_STATE_DIALING])
315
316    # If not then we need to poll for the calls themselves.
317    if len(calls_in_state) == 0:
318        event = None
319        try:
320            event = ad.ed.pop_event(
321                tel_defines.EventTelecomCallAdded,
322                tel_defines.MAX_WAIT_TIME_CONNECTION_STATE_UPDATE)
323        except queue.Empty:
324            log.info("Did not get {}".format(
325                tel_defines.EventTelecomCallAdded))
326            return False
327        finally:
328            ad.droid.telecomStopListeningForCallAdded()
329        call_id = event['data']['CallId']
330    else:
331        call_id = calls_in_state[0]
332
333    # We may still not be in-call if the call setup is going on.
334    # We wait for the call state to move to dialing.
335    log.info("call id {} droid {}".format(call_id, ad.serial))
336    if not wait_for_call_state(
337        log, ad, call_id, tel_defines.CALL_STATE_DIALING):
338        return False
339
340    # Finally check the call state.
341    return ad.droid.telecomIsInCall()
342
343def wait_for_ringing(log, ad):
344    """Wait for the droid to be in ringing state.
345
346    Args:
347        log: log object
348        ad: android device object
349
350    Returns:
351        True if success, False if fail.
352    """
353    log.info("waiting for ringing {}".format(ad.serial))
354    # Start listening for events before anything else happens.
355    ad.droid.telecomStartListeningForCallAdded()
356
357    # First check if we re in call, then simply return.
358    if ad.droid.telecomIsInCall():
359        log.info("Device already in call {}".format(ad.serial))
360        ad.droid.telecomStopListeningForCallAdded()
361        return True
362
363    call_id = None
364    # Now check if we already have calls matching the state.
365    calls_in_state = ad.droid.telecomCallGetCallIds()
366
367    for c_id in calls_in_state:
368        if ad.droid.telecomCallGetCallState(c_id) == tel_defines.CALL_STATE_RINGING:
369            return True
370
371    event = None
372    call_id = None
373    try:
374        event = ad.ed.pop_event(
375            tel_defines.EventTelecomCallAdded,
376            tel_defines.MAX_WAIT_TIME_CALLEE_RINGING)
377    except queue.Empty:
378        log.info("Did not get {} droid {}".format(
379            tel_defines.EventTelecomCallAdded,
380            ad.serial))
381        return False
382    finally:
383        ad.droid.telecomStopListeningForCallAdded()
384    call_id = event['data']['CallId']
385    log.info("wait_for_ringing call found {} dev {}".format(
386        call_id, ad.serial))
387
388    # If the call already existed then we would have returned above otherwise
389    # we will verify that the newly added call is indeed ringing.
390    if not wait_for_call_state(
391        log, ad, call_id, tel_defines.CALL_STATE_RINGING):
392        log.info("No ringing call id {} droid {}".format(
393            call_id, ad.serial))
394        return False
395    return True
396
397def wait_for_active(log, ad):
398    """Wait for the droid to be in active call state.
399
400    Args:
401        log: log object
402        ad: android device object
403
404    Returns:
405        True if success, False if fail.
406    """
407    log.info("waiting for active {}".format(ad.serial))
408    # Start listening for events before anything else happens.
409    ad.droid.telecomStartListeningForCallAdded()
410
411    call_id = None
412    # Now check if we already have calls matching the state.
413    calls_in_state = ad.droid.telecomCallGetCallIds()
414
415    if len(calls_in_state) == 0:
416        event = None
417        try:
418            event = ad.ed.pop_event(
419                tel_defines.EventTelecomCallAdded,
420                tel_defines.MAX_WAIT_TIME_CALLEE_RINGING)
421        except queue.Empty:
422            log.info("Did not get {} droid {}".format(
423                tel_defines.EventTelecomCallAdded,
424                ad.serial))
425            return False
426        finally:
427            ad.droid.telecomStopListeningForCallAdded()
428        call_id = event['data']['CallId']
429        log.info("wait_for_ringing call found {} dev {}".format(
430            call_id, ad.serial))
431    else:
432        call_id = calls_in_state[0]
433
434    # We have found a new call to be added now wait it to transition into
435    # active state.
436    if not wait_for_call_state(
437        log, ad, call_id, tel_defines.CALL_STATE_ACTIVE):
438        log.info("No active call id {} droid {}".format(
439            call_id, ad.serial))
440        return False
441    return True
442
443def wait_for_conference(log, ad, conf_calls):
444    """Wait for the droid to be in a conference with calls specified
445    in conf_calls.
446
447    Args:
448        log: log object
449        ad: android device object
450        conf_calls: List of calls that should transition to conference
451
452    Returns:
453        call_id if success, None if fail.
454    """
455    conf_calls = set(conf_calls)
456
457    log.info("waiting for conference {}".format(ad.serial))
458    ad.droid.telecomStartListeningForCallAdded()
459
460    call_ids = ad.droid.telecomCallGetCallIds()
461
462    # Check if we have a conference call and if the children match
463    for call_id in call_ids:
464        call_chld = get_call_id_children(log, ad, call_id)
465        if call_chld == conf_calls:
466            ad.droid.telecomStopListeningForCallAdded()
467            return call_id
468
469    # If not poll for new calls.
470    event = None
471    call_id = None
472    try:
473        event = ad.ed.pop_event(
474            tel_defines.EventTelecomCallAdded,
475            tel_defines.MAX_WAIT_TIME_CALLEE_RINGING)
476        log.info("wait_for_conference event {} droid {}".format(
477            event, ad.serial))
478    except queue.Empty:
479        log.error("Did not get {} droid {}".format(
480            tel_defines.EventTelecomCallAdded,
481            ad.serial))
482        return None
483    finally:
484        ad.droid.telecomStopListeningForCallAdded()
485    call_id = event['data']['CallId']
486
487    # Now poll until the children change.
488    ad.droid.telecomCallStartListeningForEvent(
489        call_id, tel_defines.EVENT_CALL_CHILDREN_CHANGED)
490
491    event = None
492    while True:
493        try:
494            event = ad.ed.pop_event(
495                tel_defines.EventTelecomCallChildrenChanged,
496                tel_defines.MAX_WAIT_TIME_CONNECTION_STATE_UPDATE)
497            call_chld = set(event['data']['Event'])
498            log.info("wait_for_conference children chld event {}".format(call_chld))
499            if call_chld == conf_calls:
500                ad.droid.telecomCallStopListeningForEvent(
501                    call_id, tel_defines.EVENT_CALL_CHILDREN_CHANGED)
502                return call_id
503        except queue.Empty:
504            log.error("Did not get {} droid {}".format(
505                tel_defines.EventTelecomCallChildrenChanged, ad.serial))
506            ad.droid.telecomCallStopListeningForEvent(
507                call_id, tel_defines.EVENT_CALL_CHILDREN_CHANGED)
508            return None
509
510def get_call_id_children(log, ad, call_id):
511    """Return the list of calls that are children to call_id
512
513    Args:
514        log: log object
515        ad: android device object
516        call_id: Conference call id
517
518    Returns:
519        List containing call_ids.
520    """
521    call = ad.droid.telecomCallGetCallById(call_id)
522    call_chld = set(call['Children'])
523    log.info("get_call_id_children droid {} call {} children {}".format(
524        ad.serial, call, call_chld))
525    return call_chld
526
527def get_calls_in_states(log, ad, call_states):
528    """Return the list of calls that are any of the states passed in call_states
529
530    Args:
531        log: log object
532        ad: android device object
533        call_states: List of desired call states
534
535    Returns:
536        List containing call_ids.
537    """
538    # Get the list of calls.
539    call_ids = ad.droid.telecomCallGetCallIds()
540    call_in_state = []
541    for call_id in call_ids:
542        call = ad.droid.telecomCallGetCallById(call_id)
543        log.info("Call id: {} desc: {}".format(call_id, call))
544        if call['State'] in call_states:
545            log.info("Adding call id {} to result set.".format(call_id))
546            call_in_state.append(call_id)
547    return call_in_state
548