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