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_contrib.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