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