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