1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.telecom.cts; 18 19 import static android.telecom.cts.TestUtils.PACKAGE; 20 import static android.telecom.cts.TestUtils.TAG; 21 import static android.telecom.cts.TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS; 22 23 import static org.hamcrest.CoreMatchers.equalTo; 24 import static org.hamcrest.CoreMatchers.not; 25 import static org.junit.Assert.assertThat; 26 27 import android.content.Context; 28 import android.content.Intent; 29 import android.net.Uri; 30 import android.os.Bundle; 31 import android.os.Handler; 32 import android.os.HandlerThread; 33 import android.telecom.Call; 34 import android.telecom.CallAudioState; 35 import android.telecom.Conference; 36 import android.telecom.Connection; 37 import android.telecom.InCallService; 38 import android.telecom.PhoneAccount; 39 import android.telecom.PhoneAccountHandle; 40 import android.telecom.TelecomManager; 41 import android.telecom.VideoProfile; 42 import android.telecom.cts.MockInCallService.InCallServiceCallbacks; 43 import android.telephony.PhoneStateListener; 44 import android.telephony.TelephonyManager; 45 import android.test.InstrumentationTestCase; 46 import android.text.TextUtils; 47 import android.util.Log; 48 import android.util.Pair; 49 50 import java.util.ArrayList; 51 import java.util.List; 52 import java.util.Objects; 53 import java.util.concurrent.CountDownLatch; 54 import java.util.concurrent.Semaphore; 55 import java.util.concurrent.TimeUnit; 56 57 /** 58 * Base class for Telecom CTS tests that require a {@link CtsConnectionService} and 59 * {@link MockInCallService} to verify Telecom functionality. 60 */ 61 public class BaseTelecomTestWithMockServices extends InstrumentationTestCase { 62 63 public static final int FLAG_REGISTER = 0x1; 64 public static final int FLAG_ENABLE = 0x2; 65 66 private static int sCounter = 5549999; 67 68 Context mContext; 69 TelecomManager mTelecomManager; 70 TelephonyManager mTelephonyManager; 71 72 TestUtils.InvokeCounter mOnBringToForegroundCounter; 73 TestUtils.InvokeCounter mOnCallAudioStateChangedCounter; 74 TestUtils.InvokeCounter mOnPostDialWaitCounter; 75 TestUtils.InvokeCounter mOnCannedTextResponsesLoadedCounter; 76 TestUtils.InvokeCounter mOnSilenceRingerCounter; 77 TestUtils.InvokeCounter mOnConnectionEventCounter; 78 TestUtils.InvokeCounter mOnExtrasChangedCounter; 79 TestUtils.InvokeCounter mOnPropertiesChangedCounter; 80 TestUtils.InvokeCounter mOnRttModeChangedCounter; 81 TestUtils.InvokeCounter mOnRttStatusChangedCounter; 82 TestUtils.InvokeCounter mOnRttInitiationFailedCounter; 83 TestUtils.InvokeCounter mOnRttRequestCounter; 84 TestUtils.InvokeCounter mOnHandoverCompleteCounter; 85 TestUtils.InvokeCounter mOnHandoverFailedCounter; 86 Bundle mPreviousExtras; 87 int mPreviousProperties = -1; 88 89 InCallServiceCallbacks mInCallCallbacks; 90 String mPreviousDefaultDialer = null; 91 MockConnectionService connectionService = null; 92 93 HandlerThread mPhoneStateListenerThread; 94 Handler mPhoneStateListenerHandler; 95 TestPhoneStateListener mPhoneStateListener; 96 97 static class TestPhoneStateListener extends PhoneStateListener { 98 /** Semaphore released for every callback invocation. */ 99 public Semaphore mCallbackSemaphore = new Semaphore(0); 100 101 List<Pair<Integer, String>> mCallStates = new ArrayList<>(); 102 103 @Override 104 public void onCallStateChanged(int state, String number) { 105 Log.i(TAG, "onCallStateChanged: state=" + state + ", number=" + number); 106 mCallStates.add(Pair.create(state, number)); 107 mCallbackSemaphore.release(); 108 } 109 } 110 111 boolean mShouldTestTelecom = true; 112 113 @Override 114 protected void setUp() throws Exception { 115 super.setUp(); 116 mContext = getInstrumentation().getContext(); 117 118 mShouldTestTelecom = TestUtils.shouldTestTelecom(mContext); 119 if (!mShouldTestTelecom) { 120 return; 121 } 122 123 mTelecomManager = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE); 124 mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); 125 126 mPreviousDefaultDialer = TestUtils.getDefaultDialer(getInstrumentation()); 127 TestUtils.setDefaultDialer(getInstrumentation(), PACKAGE); 128 setupCallbacks(); 129 130 // PhoneStateListener's public API registers the listener on the calling thread, which must 131 // be a looper thread. So we need to create and register the listener in a custom looper 132 // thread. 133 mPhoneStateListenerThread = new HandlerThread("PhoneStateListenerThread"); 134 mPhoneStateListenerThread.start(); 135 mPhoneStateListenerHandler = new Handler(mPhoneStateListenerThread.getLooper()); 136 final CountDownLatch registeredLatch = new CountDownLatch(1); 137 mPhoneStateListenerHandler.post(new Runnable() { 138 @Override 139 public void run() { 140 mPhoneStateListener = new TestPhoneStateListener(); 141 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); 142 registeredLatch.countDown(); 143 } 144 }); 145 registeredLatch.await( 146 TestUtils.WAIT_FOR_PHONE_STATE_LISTENER_REGISTERED_TIMEOUT_S, TimeUnit.SECONDS); 147 } 148 149 @Override 150 protected void tearDown() throws Exception { 151 super.tearDown(); 152 if (!mShouldTestTelecom) { 153 return; 154 } 155 156 final CountDownLatch unregisteredLatch = new CountDownLatch(1); 157 mPhoneStateListenerHandler.post(new Runnable() { 158 @Override 159 public void run() { 160 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE); 161 unregisteredLatch.countDown(); 162 } 163 }); 164 unregisteredLatch.await( 165 TestUtils.WAIT_FOR_PHONE_STATE_LISTENER_REGISTERED_TIMEOUT_S, TimeUnit.SECONDS); 166 mPhoneStateListenerThread.quit(); 167 168 cleanupCalls(); 169 if (!TextUtils.isEmpty(mPreviousDefaultDialer)) { 170 TestUtils.setDefaultDialer(getInstrumentation(), mPreviousDefaultDialer); 171 } 172 tearDownConnectionService(TestUtils.TEST_PHONE_ACCOUNT_HANDLE); 173 assertMockInCallServiceUnbound(); 174 } 175 176 protected PhoneAccount setupConnectionService(MockConnectionService connectionService, 177 int flags) throws Exception { 178 if (connectionService != null) { 179 this.connectionService = connectionService; 180 } else { 181 // Generate a vanilla mock connection service, if not provided. 182 this.connectionService = new MockConnectionService(); 183 } 184 CtsConnectionService.setUp(this.connectionService); 185 186 if ((flags & FLAG_REGISTER) != 0) { 187 mTelecomManager.registerPhoneAccount(TestUtils.TEST_PHONE_ACCOUNT); 188 } 189 if ((flags & FLAG_ENABLE) != 0) { 190 TestUtils.enablePhoneAccount(getInstrumentation(), TestUtils.TEST_PHONE_ACCOUNT_HANDLE); 191 // Wait till the adb commands have executed and account is enabled in Telecom database. 192 assertPhoneAccountEnabled(TestUtils.TEST_PHONE_ACCOUNT_HANDLE); 193 } 194 195 return TestUtils.TEST_PHONE_ACCOUNT; 196 } 197 198 protected void tearDownConnectionService(PhoneAccountHandle accountHandle) throws Exception { 199 if (this.connectionService != null) { 200 assertNumConnections(this.connectionService, 0); 201 } 202 mTelecomManager.unregisterPhoneAccount(accountHandle); 203 CtsConnectionService.tearDown(); 204 assertCtsConnectionServiceUnbound(); 205 this.connectionService = null; 206 } 207 208 protected void startCallTo(Uri address, PhoneAccountHandle accountHandle) { 209 final Intent intent = new Intent(Intent.ACTION_CALL, address); 210 if (accountHandle != null) { 211 intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, accountHandle); 212 } 213 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 214 mContext.startActivity(intent); 215 } 216 217 void sleep(long ms) { 218 try { 219 Thread.sleep(ms); 220 } catch (InterruptedException e) { 221 } 222 } 223 224 private void setupCallbacks() { 225 mInCallCallbacks = new InCallServiceCallbacks() { 226 @Override 227 public void onCallAdded(Call call, int numCalls) { 228 Log.i(TAG, "onCallAdded, Call: " + call + ", Num Calls: " + numCalls); 229 this.lock.release(); 230 } 231 @Override 232 public void onCallRemoved(Call call, int numCalls) { 233 Log.i(TAG, "onCallRemoved, Call: " + call + ", Num Calls: " + numCalls); 234 } 235 @Override 236 public void onParentChanged(Call call, Call parent) { 237 Log.i(TAG, "onParentChanged, Call: " + call + ", Parent: " + parent); 238 this.lock.release(); 239 } 240 @Override 241 public void onChildrenChanged(Call call, List<Call> children) { 242 Log.i(TAG, "onChildrenChanged, Call: " + call + "Children: " + children); 243 this.lock.release(); 244 } 245 @Override 246 public void onConferenceableCallsChanged(Call call, List<Call> conferenceableCalls) { 247 Log.i(TAG, "onConferenceableCallsChanged, Call: " + call + ", Conferenceables: " + 248 conferenceableCalls); 249 } 250 @Override 251 public void onDetailsChanged(Call call, Call.Details details) { 252 Log.i(TAG, "onDetailsChanged, Call: " + call + ", Details: " + details); 253 if (!areBundlesEqual(mPreviousExtras, details.getExtras())) { 254 mOnExtrasChangedCounter.invoke(call, details); 255 } 256 mPreviousExtras = details.getExtras(); 257 258 if (mPreviousProperties != details.getCallProperties()) { 259 mOnPropertiesChangedCounter.invoke(call, details); 260 Log.i(TAG, "onDetailsChanged; properties changed from " + Call.Details.propertiesToString(mPreviousProperties) + 261 " to " + Call.Details.propertiesToString(details.getCallProperties())); 262 } 263 mPreviousProperties = details.getCallProperties(); 264 } 265 @Override 266 public void onCallDestroyed(Call call) { 267 Log.i(TAG, "onCallDestroyed, Call: " + call); 268 } 269 @Override 270 public void onCallStateChanged(Call call, int newState) { 271 Log.i(TAG, "onCallStateChanged, Call: " + call + ", New State: " + newState); 272 } 273 @Override 274 public void onBringToForeground(boolean showDialpad) { 275 mOnBringToForegroundCounter.invoke(showDialpad); 276 } 277 @Override 278 public void onCallAudioStateChanged(CallAudioState audioState) { 279 Log.i(TAG, "onCallAudioStateChanged, audioState: " + audioState); 280 mOnCallAudioStateChangedCounter.invoke(audioState); 281 } 282 @Override 283 public void onPostDialWait(Call call, String remainingPostDialSequence) { 284 mOnPostDialWaitCounter.invoke(call, remainingPostDialSequence); 285 } 286 @Override 287 public void onCannedTextResponsesLoaded(Call call, List<String> cannedTextResponses) { 288 mOnCannedTextResponsesLoadedCounter.invoke(call, cannedTextResponses); 289 } 290 @Override 291 public void onConnectionEvent(Call call, String event, Bundle extras) { 292 mOnConnectionEventCounter.invoke(call, event, extras); 293 } 294 295 @Override 296 public void onSilenceRinger() { 297 Log.i(TAG, "onSilenceRinger"); 298 mOnSilenceRingerCounter.invoke(); 299 } 300 301 @Override 302 public void onRttModeChanged(Call call, int mode) { 303 mOnRttModeChangedCounter.invoke(call, mode); 304 } 305 306 @Override 307 public void onRttStatusChanged(Call call, boolean enabled, Call.RttCall rttCall) { 308 mOnRttStatusChangedCounter.invoke(call, enabled, rttCall); 309 } 310 311 @Override 312 public void onRttRequest(Call call, int id) { 313 mOnRttRequestCounter.invoke(call, id); 314 } 315 316 @Override 317 public void onRttInitiationFailure(Call call, int reason) { 318 mOnRttInitiationFailedCounter.invoke(call, reason); 319 } 320 321 @Override 322 public void onHandoverComplete(Call call) { 323 mOnHandoverCompleteCounter.invoke(call); 324 } 325 326 @Override 327 public void onHandoverFailed(Call call, int reason) { 328 mOnHandoverFailedCounter.invoke(call, reason); 329 } 330 }; 331 332 MockInCallService.setCallbacks(mInCallCallbacks); 333 334 // TODO: If more InvokeCounters are added in the future, consider consolidating them into a 335 // single Collection. 336 mOnBringToForegroundCounter = new TestUtils.InvokeCounter("OnBringToForeground"); 337 mOnCallAudioStateChangedCounter = new TestUtils.InvokeCounter("OnCallAudioStateChanged"); 338 mOnPostDialWaitCounter = new TestUtils.InvokeCounter("OnPostDialWait"); 339 mOnCannedTextResponsesLoadedCounter = new TestUtils.InvokeCounter("OnCannedTextResponsesLoaded"); 340 mOnSilenceRingerCounter = new TestUtils.InvokeCounter("OnSilenceRinger"); 341 mOnConnectionEventCounter = new TestUtils.InvokeCounter("OnConnectionEvent"); 342 mOnExtrasChangedCounter = new TestUtils.InvokeCounter("OnDetailsChangedCounter"); 343 mOnPropertiesChangedCounter = new TestUtils.InvokeCounter("OnPropertiesChangedCounter"); 344 mOnRttModeChangedCounter = new TestUtils.InvokeCounter("mOnRttModeChangedCounter"); 345 mOnRttStatusChangedCounter = new TestUtils.InvokeCounter("mOnRttStatusChangedCounter"); 346 mOnRttInitiationFailedCounter = 347 new TestUtils.InvokeCounter("mOnRttInitiationFailedCounter"); 348 mOnRttRequestCounter = new TestUtils.InvokeCounter("mOnRttRequestCounter"); 349 mOnHandoverCompleteCounter = new TestUtils.InvokeCounter("mOnHandoverCompleteCounter"); 350 mOnHandoverFailedCounter = new TestUtils.InvokeCounter("mOnHandoverFailedCounter"); 351 } 352 353 /** 354 * Puts Telecom in a state where there is an incoming call provided by the 355 * {@link CtsConnectionService} which can be tested. 356 */ 357 void addAndVerifyNewIncomingCall(Uri incomingHandle, Bundle extras) { 358 assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits()); 359 int currentCallCount = 0; 360 if (mInCallCallbacks.getService() != null) { 361 currentCallCount = mInCallCallbacks.getService().getCallCount(); 362 } 363 364 if (extras == null) { 365 extras = new Bundle(); 366 } 367 extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, incomingHandle); 368 mTelecomManager.addNewIncomingCall(TestUtils.TEST_PHONE_ACCOUNT_HANDLE, extras); 369 370 try { 371 if (!mInCallCallbacks.lock.tryAcquire(TestUtils.WAIT_FOR_CALL_ADDED_TIMEOUT_S, 372 TimeUnit.SECONDS)) { 373 fail("No call added to InCallService."); 374 } 375 } catch (InterruptedException e) { 376 Log.i(TAG, "Test interrupted!"); 377 } 378 379 assertEquals("InCallService should contain 1 more call after adding a call.", 380 currentCallCount + 1, 381 mInCallCallbacks.getService().getCallCount()); 382 } 383 384 /** 385 * Puts Telecom in a state where there is an active call provided by the 386 * {@link CtsConnectionService} which can be tested. 387 */ 388 void placeAndVerifyCall() { 389 placeAndVerifyCall(null); 390 } 391 392 /** 393 * Puts Telecom in a state where there is an active call provided by the 394 * {@link CtsConnectionService} which can be tested. 395 * 396 * @param videoState the video state of the call. 397 */ 398 void placeAndVerifyCall(int videoState) { 399 placeAndVerifyCall(null, videoState); 400 } 401 402 /** 403 * Puts Telecom in a state where there is an active call provided by the 404 * {@link CtsConnectionService} which can be tested. 405 */ 406 void placeAndVerifyCall(Bundle extras) { 407 placeAndVerifyCall(extras, VideoProfile.STATE_AUDIO_ONLY); 408 } 409 410 /** 411 * Puts Telecom in a state where there is an active call provided by the 412 * {@link CtsConnectionService} which can be tested. 413 */ 414 void placeAndVerifyCall(Bundle extras, int videoState) { 415 assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits()); 416 int currentCallCount = 0; 417 if (mInCallCallbacks.getService() != null) { 418 currentCallCount = mInCallCallbacks.getService().getCallCount(); 419 } 420 int currentConnectionCount = getNumberOfConnections(); 421 placeNewCallWithPhoneAccount(extras, videoState); 422 423 try { 424 if (!mInCallCallbacks.lock.tryAcquire(TestUtils.WAIT_FOR_CALL_ADDED_TIMEOUT_S, 425 TimeUnit.SECONDS)) { 426 fail("No call added to InCallService."); 427 } 428 } catch (InterruptedException e) { 429 Log.i(TAG, "Test interrupted!"); 430 } 431 432 assertEquals("InCallService should contain 1 more call after adding a call.", 433 currentCallCount + 1, 434 mInCallCallbacks.getService().getCallCount()); 435 436 // The connectionService.lock is released in 437 // MockConnectionService#onCreateOutgoingConnection, however the connection will not 438 // actually be added to the list of connections in the ConnectionService until shortly 439 // afterwards. So there is still a potential for the lock to be released before it would 440 // be seen by calls to ConnectionService#getAllConnections(). 441 // We will wait here until the list of connections includes one more connection to ensure 442 // that placing the call has fully completed. 443 final int expectedConnectionCount = currentConnectionCount + 1; 444 assertCSConnections(expectedConnectionCount); 445 } 446 447 int getNumberOfConnections() { 448 return CtsConnectionService.getAllConnectionsFromTelecom().size(); 449 } 450 451 MockConnection verifyConnectionForOutgoingCall() { 452 // Assuming only 1 connection present 453 return verifyConnectionForOutgoingCall(0); 454 } 455 456 MockConnection verifyConnectionForOutgoingCall(int connectionIndex) { 457 try { 458 if (!connectionService.lock.tryAcquire(TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 459 TimeUnit.MILLISECONDS)) { 460 fail("No outgoing call connection requested by Telecom"); 461 } 462 } catch (InterruptedException e) { 463 Log.i(TAG, "Test interrupted!"); 464 } 465 466 assertThat("Telecom should create outgoing connection for outgoing call", 467 connectionService.outgoingConnections.size(), not(equalTo(0))); 468 MockConnection connection = connectionService.outgoingConnections.get(connectionIndex); 469 return connection; 470 } 471 472 MockConnection verifyConnectionForIncomingCall() { 473 // Assuming only 1 connection present 474 return verifyConnectionForIncomingCall(0); 475 } 476 477 MockConnection verifyConnectionForIncomingCall(int connectionIndex) { 478 try { 479 if (!connectionService.lock.tryAcquire(TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 480 TimeUnit.MILLISECONDS)) { 481 fail("No outgoing call connection requested by Telecom"); 482 } 483 } catch (InterruptedException e) { 484 Log.i(TAG, "Test interrupted!"); 485 } 486 487 assertThat("Telecom should create incoming connections for incoming calls", 488 connectionService.incomingConnections.size(), not(equalTo(0))); 489 MockConnection connection = connectionService.incomingConnections.get(connectionIndex); 490 setAndVerifyConnectionForIncomingCall(connection); 491 return connection; 492 } 493 494 void setAndVerifyConnectionForIncomingCall(MockConnection connection) { 495 connection.setRinging(); 496 assertConnectionState(connection, Connection.STATE_RINGING); 497 } 498 499 void setAndVerifyConferenceablesForOutgoingConnection(int connectionIndex) { 500 assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits()); 501 // Make all other outgoing connections as conferenceable with this connection. 502 MockConnection connection = connectionService.outgoingConnections.get(connectionIndex); 503 List<Connection> confConnections = 504 new ArrayList<>(connectionService.outgoingConnections.size()); 505 for (Connection c : connectionService.outgoingConnections) { 506 if (c != connection) { 507 confConnections.add(c); 508 } 509 } 510 connection.setConferenceableConnections(confConnections); 511 assertEquals(connection.getConferenceables(), confConnections); 512 } 513 514 void addConferenceCall(Call call1, Call call2) { 515 assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits()); 516 int currentConfCallCount = 0; 517 if (mInCallCallbacks.getService() != null) { 518 currentConfCallCount = mInCallCallbacks.getService().getConferenceCallCount(); 519 } 520 // Verify that the calls have each other on their conferenceable list before proceeding 521 List<Call> callConfList = new ArrayList<>(); 522 callConfList.add(call2); 523 assertCallConferenceableList(call1, callConfList); 524 525 callConfList.clear(); 526 callConfList.add(call1); 527 assertCallConferenceableList(call2, callConfList); 528 529 call1.conference(call2); 530 531 /** 532 * We should have 1 onCallAdded, 2 onChildrenChanged and 2 onParentChanged invoked, so 533 * we should have 5 available permits on the incallService lock. 534 */ 535 try { 536 if (!mInCallCallbacks.lock.tryAcquire(5, 3, TimeUnit.SECONDS)) { 537 fail("Conference addition failed."); 538 } 539 } catch (InterruptedException e) { 540 Log.i(TAG, "Test interrupted!"); 541 } 542 543 assertEquals("InCallService should contain 1 more call after adding a conf call.", 544 currentConfCallCount + 1, 545 mInCallCallbacks.getService().getConferenceCallCount()); 546 } 547 548 void splitFromConferenceCall(Call call1) { 549 assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits()); 550 551 call1.splitFromConference(); 552 /** 553 * We should have 1 onChildrenChanged and 1 onParentChanged invoked, so 554 * we should have 2 available permits on the incallService lock. 555 */ 556 try { 557 if (!mInCallCallbacks.lock.tryAcquire(2, 3, TimeUnit.SECONDS)) { 558 fail("Conference split failed"); 559 } 560 } catch (InterruptedException e) { 561 Log.i(TAG, "Test interrupted!"); 562 } 563 } 564 565 MockConference verifyConferenceForOutgoingCall() { 566 try { 567 if (!connectionService.lock.tryAcquire(TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 568 TimeUnit.MILLISECONDS)) { 569 fail("No outgoing conference requested by Telecom"); 570 } 571 } catch (InterruptedException e) { 572 Log.i(TAG, "Test interrupted!"); 573 } 574 // Return the newly created conference object to the caller 575 MockConference conference = connectionService.conferences.get(0); 576 setAndVerifyConferenceForOutgoingCall(conference); 577 return conference; 578 } 579 580 void setAndVerifyConferenceForOutgoingCall(MockConference conference) { 581 conference.setActive(); 582 assertConferenceState(conference, Connection.STATE_ACTIVE); 583 } 584 585 void verifyPhoneStateListenerCallbacksForCall(int expectedCallState, String expectedNumber) 586 throws Exception { 587 assertTrue(mPhoneStateListener.mCallbackSemaphore.tryAcquire( 588 TestUtils.WAIT_FOR_PHONE_STATE_LISTENER_CALLBACK_TIMEOUT_S, TimeUnit.SECONDS)); 589 // At this point we can only be sure that we got AN update, but not necessarily the one we 590 // are looking for; wait until we see the state we want before verifying further. 591 waitUntilConditionIsTrueOrTimeout(new Condition() { 592 @Override 593 public Object expected() { 594 return true; 595 } 596 597 @Override 598 public Object actual() { 599 return mPhoneStateListener.mCallStates 600 .stream() 601 .filter(p -> p.first.equals( 602 expectedCallState) 603 && p.second.equals( 604 expectedNumber)) 605 .count() > 0; 606 } 607 }, 608 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 609 "Expected call state " + expectedCallState + " and number " 610 + expectedNumber); 611 612 613 // Get the most recent callback; it is possible that there was an initial state reported due 614 // to the fact that TelephonyManager will sometimes give an initial state back to the caller 615 // when the listener is registered. 616 Pair<Integer, String> callState = mPhoneStateListener.mCallStates.get( 617 mPhoneStateListener.mCallStates.size() - 1); 618 assertEquals(expectedCallState, (int) callState.first); 619 // Note: We do NOT check the phone number here. Due to changes in how the phone state 620 // broadcast is sent, the caller may receive multiple broadcasts, and the number will be 621 // present in one or the other. We waited for a full matching broadcast above so we can 622 // be sure the number was reported as expected. 623 } 624 625 /** 626 * Disconnect the created test call and verify that Telecom has cleared all calls. 627 */ 628 void cleanupCalls() { 629 if (mInCallCallbacks != null && mInCallCallbacks.getService() != null) { 630 mInCallCallbacks.getService().disconnectAllConferenceCalls(); 631 mInCallCallbacks.getService().disconnectAllCalls(); 632 assertNumConferenceCalls(mInCallCallbacks.getService(), 0); 633 assertNumCalls(mInCallCallbacks.getService(), 0); 634 } 635 } 636 637 /** 638 * Place a new outgoing call via the {@link CtsConnectionService} 639 */ 640 private void placeNewCallWithPhoneAccount(Bundle extras, int videoState) { 641 if (extras == null) { 642 extras = new Bundle(); 643 } 644 if (!extras.containsKey(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE)) { 645 extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, 646 TestUtils.TEST_PHONE_ACCOUNT_HANDLE); 647 } 648 649 if (!VideoProfile.isAudioOnly(videoState)) { 650 extras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState); 651 } 652 653 mTelecomManager.placeCall(createTestNumber(), extras); 654 } 655 656 /** 657 * Create a new number each time for a new test. Telecom has special logic to reuse certain 658 * calls if multiple calls to the same number are placed within a short period of time which 659 * can cause certain tests to fail. 660 */ 661 Uri createTestNumber() { 662 return Uri.fromParts("tel", String.valueOf(++sCounter), null); 663 } 664 665 public static Uri getTestNumber() { 666 return Uri.fromParts("tel", String.valueOf(sCounter), null); 667 } 668 669 void assertNumCalls(final MockInCallService inCallService, final int numCalls) { 670 waitUntilConditionIsTrueOrTimeout(new Condition() { 671 @Override 672 public Object expected() { 673 return numCalls; 674 } 675 @Override 676 public Object actual() { 677 return inCallService.getCallCount(); 678 } 679 }, 680 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 681 "InCallService should contain " + numCalls + " calls." 682 ); 683 } 684 685 void assertNumConferenceCalls(final MockInCallService inCallService, final int numCalls) { 686 waitUntilConditionIsTrueOrTimeout(new Condition() { 687 @Override 688 public Object expected() { 689 return numCalls; 690 } 691 @Override 692 public Object actual() { 693 return inCallService.getConferenceCallCount(); 694 } 695 }, 696 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 697 "InCallService should contain " + numCalls + " conference calls." 698 ); 699 } 700 701 void assertCSConnections(final int numConnections) { 702 waitUntilConditionIsTrueOrTimeout(new Condition() { 703 @Override 704 public Object expected() { 705 return numConnections; 706 } 707 708 @Override 709 public Object actual() { 710 return CtsConnectionService 711 .getAllConnectionsFromTelecom() 712 .size(); 713 } 714 }, 715 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 716 "ConnectionService should contain " + numConnections + " connections." 717 ); 718 } 719 720 void assertNumConnections(final MockConnectionService connService, final int numConnections) { 721 waitUntilConditionIsTrueOrTimeout(new Condition() { 722 @Override 723 public Object expected() { 724 return numConnections; 725 } 726 @Override 727 public Object actual() { 728 return connService.getAllConnections().size(); 729 } 730 }, 731 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 732 "ConnectionService should contain " + numConnections + " connections." 733 ); 734 } 735 736 void assertMuteState(final InCallService incallService, final boolean isMuted) { 737 waitUntilConditionIsTrueOrTimeout( 738 new Condition() { 739 @Override 740 public Object expected() { 741 return isMuted; 742 } 743 744 @Override 745 public Object actual() { 746 final CallAudioState state = incallService.getCallAudioState(); 747 return state == null ? null : state.isMuted(); 748 } 749 }, 750 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 751 "Phone's mute state should be: " + isMuted 752 ); 753 } 754 755 void assertMuteState(final MockConnection connection, final boolean isMuted) { 756 waitUntilConditionIsTrueOrTimeout( 757 new Condition() { 758 @Override 759 public Object expected() { 760 return isMuted; 761 } 762 763 @Override 764 public Object actual() { 765 final CallAudioState state = connection.getCallAudioState(); 766 return state == null ? null : state.isMuted(); 767 } 768 }, 769 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 770 "Connection's mute state should be: " + isMuted 771 ); 772 } 773 774 void assertAudioRoute(final InCallService incallService, final int route) { 775 waitUntilConditionIsTrueOrTimeout( 776 new Condition() { 777 @Override 778 public Object expected() { 779 return route; 780 } 781 782 @Override 783 public Object actual() { 784 final CallAudioState state = incallService.getCallAudioState(); 785 return state == null ? null : state.getRoute(); 786 } 787 }, 788 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 789 "Phone's audio route should be: " + route 790 ); 791 } 792 793 void assertNotAudioRoute(final InCallService incallService, final int route) { 794 waitUntilConditionIsTrueOrTimeout( 795 new Condition() { 796 @Override 797 public Object expected() { 798 return new Boolean(true); 799 } 800 801 @Override 802 public Object actual() { 803 final CallAudioState state = incallService.getCallAudioState(); 804 return route != state.getRoute(); 805 } 806 }, 807 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 808 "Phone's audio route should not be: " + route 809 ); 810 } 811 812 void assertAudioRoute(final MockConnection connection, final int route) { 813 waitUntilConditionIsTrueOrTimeout( 814 new Condition() { 815 @Override 816 public Object expected() { 817 return route; 818 } 819 820 @Override 821 public Object actual() { 822 final CallAudioState state = ((Connection) connection).getCallAudioState(); 823 return state == null ? null : state.getRoute(); 824 } 825 }, 826 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 827 "Connection's audio route should be: " + route 828 ); 829 } 830 831 void assertConnectionState(final Connection connection, final int state) { 832 waitUntilConditionIsTrueOrTimeout( 833 new Condition() { 834 @Override 835 public Object expected() { 836 return state; 837 } 838 839 @Override 840 public Object actual() { 841 return connection.getState(); 842 } 843 }, 844 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 845 "Connection should be in state " + state 846 ); 847 } 848 849 void assertCallState(final Call call, final int state) { 850 waitUntilConditionIsTrueOrTimeout( 851 new Condition() { 852 @Override 853 public Object expected() { 854 return state; 855 } 856 857 @Override 858 public Object actual() { 859 return call.getState(); 860 } 861 }, 862 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 863 "Call: " + call + " should be in state " + state 864 ); 865 } 866 867 void assertCallConferenceableList(final Call call, final List<Call> conferenceableList) { 868 waitUntilConditionIsTrueOrTimeout( 869 new Condition() { 870 @Override 871 public Object expected() { 872 return conferenceableList; 873 } 874 875 @Override 876 public Object actual() { 877 return call.getConferenceableCalls(); 878 } 879 }, 880 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 881 "Call: " + call + " does not have the correct conferenceable call list." 882 ); 883 } 884 885 void assertDtmfString(final MockConnection connection, final String dtmfString) { 886 waitUntilConditionIsTrueOrTimeout(new Condition() { 887 @Override 888 public Object expected() { 889 return dtmfString; 890 } 891 892 @Override 893 public Object actual() { 894 return connection.getDtmfString(); 895 } 896 }, 897 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 898 "DTMF string should be equivalent to entered DTMF characters: " + dtmfString 899 ); 900 } 901 902 void assertDtmfString(final MockConference conference, final String dtmfString) { 903 waitUntilConditionIsTrueOrTimeout(new Condition() { 904 @Override 905 public Object expected() { 906 return dtmfString; 907 } 908 909 @Override 910 public Object actual() { 911 return conference.getDtmfString(); 912 } 913 }, 914 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 915 "DTMF string should be equivalent to entered DTMF characters: " + dtmfString 916 ); 917 } 918 919 void assertCallDisplayName(final Call call, final String name) { 920 waitUntilConditionIsTrueOrTimeout( 921 new Condition() { 922 @Override 923 public Object expected() { 924 return name; 925 } 926 927 @Override 928 public Object actual() { 929 return call.getDetails().getCallerDisplayName(); 930 } 931 }, 932 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 933 "Call should have display name: " + name 934 ); 935 } 936 937 void assertConnectionCallDisplayName(final Connection connection, final String name) { 938 waitUntilConditionIsTrueOrTimeout( 939 new Condition() { 940 @Override 941 public Object expected() { 942 return name; 943 } 944 945 @Override 946 public Object actual() { 947 return connection.getCallerDisplayName(); 948 } 949 }, 950 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 951 "Connection should have display name: " + name 952 ); 953 } 954 955 void assertDisconnectReason(final Connection connection, final String disconnectReason) { 956 waitUntilConditionIsTrueOrTimeout( 957 new Condition() { 958 @Override 959 public Object expected() { 960 return disconnectReason; 961 } 962 963 @Override 964 public Object actual() { 965 return connection.getDisconnectCause().getReason(); 966 } 967 }, 968 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 969 "Connection should have been disconnected with reason: " + disconnectReason 970 ); 971 } 972 973 void assertConferenceState(final Conference conference, final int state) { 974 waitUntilConditionIsTrueOrTimeout( 975 new Condition() { 976 @Override 977 public Object expected() { 978 return state; 979 } 980 981 @Override 982 public Object actual() { 983 return conference.getState(); 984 } 985 }, 986 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 987 "Conference should be in state " + state 988 ); 989 } 990 991 /** 992 * Checks all fields of two PhoneAccounts for equality, with the exception of the enabled state. 993 * Should only be called after assertPhoneAccountRegistered when it can be guaranteed 994 * that the PhoneAccount is registered. 995 * @param expected The expected PhoneAccount. 996 * @param actual The actual PhoneAccount. 997 */ 998 void assertPhoneAccountEquals(final PhoneAccount expected, 999 final PhoneAccount actual) { 1000 assertEquals(expected.getAddress(), actual.getAddress()); 1001 assertEquals(expected.getAccountHandle(), actual.getAccountHandle()); 1002 assertEquals(expected.getCapabilities(), actual.getCapabilities()); 1003 assertTrue(areBundlesEqual(expected.getExtras(), actual.getExtras())); 1004 assertEquals(expected.getHighlightColor(), actual.getHighlightColor()); 1005 assertEquals(expected.getIcon(), actual.getIcon()); 1006 assertEquals(expected.getLabel(), actual.getLabel()); 1007 assertEquals(expected.getShortDescription(), actual.getShortDescription()); 1008 assertEquals(expected.getSubscriptionAddress(), actual.getSubscriptionAddress()); 1009 assertEquals(expected.getSupportedUriSchemes(), actual.getSupportedUriSchemes()); 1010 } 1011 1012 void assertPhoneAccountRegistered(final PhoneAccountHandle handle) { 1013 waitUntilConditionIsTrueOrTimeout( 1014 new Condition() { 1015 @Override 1016 public Object expected() { 1017 return true; 1018 } 1019 1020 @Override 1021 public Object actual() { 1022 return mTelecomManager.getPhoneAccount(handle) != null; 1023 } 1024 }, 1025 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 1026 "Phone account registration failed for " + handle 1027 ); 1028 } 1029 1030 void assertPhoneAccountEnabled(final PhoneAccountHandle handle) { 1031 waitUntilConditionIsTrueOrTimeout( 1032 new Condition() { 1033 @Override 1034 public Object expected() { 1035 return true; 1036 } 1037 1038 @Override 1039 public Object actual() { 1040 PhoneAccount phoneAccount = mTelecomManager.getPhoneAccount(handle); 1041 return (phoneAccount != null && phoneAccount.isEnabled()); 1042 } 1043 }, 1044 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 1045 "Phone account enable failed for " + handle 1046 ); 1047 } 1048 1049 void assertCtsConnectionServiceUnbound() { 1050 if (CtsConnectionService.isBound()) { 1051 assertTrue("CtsConnectionService not yet unbound!", 1052 CtsConnectionService.waitForUnBinding()); 1053 } 1054 } 1055 1056 void assertMockInCallServiceUnbound() { 1057 waitUntilConditionIsTrueOrTimeout( 1058 new Condition() { 1059 @Override 1060 public Object expected() { 1061 return false; 1062 } 1063 1064 @Override 1065 public Object actual() { 1066 return MockInCallService.isServiceBound(); 1067 } 1068 }, 1069 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 1070 "MockInCallService not yet unbound!" 1071 ); 1072 } 1073 1074 void assertIsInCall(boolean isIncall) { 1075 waitUntilConditionIsTrueOrTimeout( 1076 new Condition() { 1077 @Override 1078 public Object expected() { 1079 return isIncall; 1080 } 1081 1082 @Override 1083 public Object actual() { 1084 return mTelecomManager.isInCall(); 1085 } 1086 }, 1087 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 1088 "Expected isInCall to be " + isIncall 1089 ); 1090 } 1091 1092 void assertIsInManagedCall(boolean isIncall) { 1093 waitUntilConditionIsTrueOrTimeout( 1094 new Condition() { 1095 @Override 1096 public Object expected() { 1097 return isIncall; 1098 } 1099 1100 @Override 1101 public Object actual() { 1102 return mTelecomManager.isInManagedCall(); 1103 } 1104 }, 1105 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 1106 "Expected isInManagedCall to be " + isIncall 1107 ); 1108 } 1109 1110 /** 1111 * Asserts that a call's properties are as expected. 1112 * 1113 * @param call The call. 1114 * @param properties The expected properties. 1115 */ 1116 public void assertCallProperties(final Call call, final int properties) { 1117 waitUntilConditionIsTrueOrTimeout( 1118 new Condition() { 1119 @Override 1120 public Object expected() { 1121 return true; 1122 } 1123 1124 @Override 1125 public Object actual() { 1126 return call.getDetails().hasProperty(properties); 1127 } 1128 }, 1129 TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 1130 "Call should have properties " + properties 1131 ); 1132 } 1133 1134 /** 1135 * Asserts that a call's capabilities are as expected. 1136 * 1137 * @param call The call. 1138 * @param capabilities The expected capabiltiies. 1139 */ 1140 public void assertCallCapabilities(final Call call, final int capabilities) { 1141 waitUntilConditionIsTrueOrTimeout( 1142 new Condition() { 1143 @Override 1144 public Object expected() { 1145 return true; 1146 } 1147 1148 @Override 1149 public Object actual() { 1150 return (call.getDetails().getCallCapabilities() & capabilities) == 1151 capabilities; 1152 } 1153 }, 1154 TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 1155 "Call should have properties " + capabilities 1156 ); 1157 } 1158 1159 void waitUntilConditionIsTrueOrTimeout(Condition condition, long timeout, 1160 String description) { 1161 final long start = System.currentTimeMillis(); 1162 while (!condition.expected().equals(condition.actual()) 1163 && System.currentTimeMillis() - start < timeout) { 1164 sleep(50); 1165 } 1166 assertEquals(description, condition.expected(), condition.actual()); 1167 } 1168 1169 /** 1170 * Performs some work, and waits for the condition to be met. If the condition is not met in 1171 * each step of the loop, the work is performed again. 1172 * 1173 * @param work The work to perform. 1174 * @param condition The condition. 1175 * @param timeout The timeout. 1176 * @param description Description of the work being performed. 1177 */ 1178 void doWorkAndWaitUntilConditionIsTrueOrTimeout(Work work, Condition condition, long timeout, 1179 String description) { 1180 final long start = System.currentTimeMillis(); 1181 work.doWork(); 1182 while (!condition.expected().equals(condition.actual()) 1183 && System.currentTimeMillis() - start < timeout) { 1184 sleep(50); 1185 work.doWork(); 1186 } 1187 assertEquals(description, condition.expected(), condition.actual()); 1188 } 1189 1190 protected interface Condition { 1191 Object expected(); 1192 Object actual(); 1193 } 1194 1195 protected interface Work { 1196 void doWork(); 1197 } 1198 1199 public static boolean areBundlesEqual(Bundle extras, Bundle newExtras) { 1200 if (extras == null || newExtras == null) { 1201 return extras == newExtras; 1202 } 1203 1204 if (extras.size() != newExtras.size()) { 1205 return false; 1206 } 1207 1208 for (String key : extras.keySet()) { 1209 if (key != null) { 1210 final Object value = extras.get(key); 1211 final Object newValue = newExtras.get(key); 1212 if (!Objects.equals(value, newValue)) { 1213 return false; 1214 } 1215 } 1216 } 1217 return true; 1218 } 1219 } 1220