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 android.os.Bundle; 22 import android.telecom.Call; 23 import android.telecom.Conference; 24 import android.telecom.Connection; 25 import android.telecom.ConnectionRequest; 26 import android.telecom.DisconnectCause; 27 import android.telecom.PhoneAccount; 28 import android.telecom.PhoneAccountHandle; 29 import android.telecom.StatusHints; 30 import android.telecom.TelecomManager; 31 import android.telecom.VideoProfile; 32 import android.util.Log; 33 34 import java.util.ArrayList; 35 import java.util.Arrays; 36 import java.util.List; 37 38 /** 39 * Extended suite of tests that use {@link CtsConnectionService} and {@link MockInCallService} to 40 * verify the functionality of Call Conferencing. 41 */ 42 public class ConferenceTest extends BaseTelecomTestWithMockServices { 43 44 private static final String TEST_EXTRA_KEY_1 = "android.telecom.test.KEY1"; 45 private static final String TEST_EXTRA_KEY_2 = "android.telecom.test.KEY2"; 46 private static final String TEST_EXTRA_VALUE_1 = "test"; 47 private static final int TEST_EXTRA_VALUE_2 = 42; 48 49 public static final int CONF_CAPABILITIES = Connection.CAPABILITY_SEPARATE_FROM_CONFERENCE | 50 Connection.CAPABILITY_DISCONNECT_FROM_CONFERENCE | Connection.CAPABILITY_HOLD | 51 Connection.CAPABILITY_MERGE_CONFERENCE | Connection.CAPABILITY_SWAP_CONFERENCE; 52 53 private Call mCall1, mCall2; 54 private MockConnection mConnection1, mConnection2; 55 MockInCallService mInCallService; 56 Conference mConferenceObject; 57 MockConference mConferenceVerficationObject; 58 59 @Override 60 protected void setUp() throws Exception { 61 super.setUp(); 62 if (mShouldTestTelecom) { 63 addOutgoingCalls(); 64 addConferenceCall(mCall1, mCall2); 65 mConferenceVerficationObject = verifyConferenceForOutgoingCall(); 66 // Use vanilla conference object so that the CTS coverage tool detects the usage. 67 mConferenceObject = mConferenceVerficationObject; 68 verifyConferenceObject(mConferenceObject, mConnection1, mConnection2); 69 } 70 } 71 72 public void testConferenceCreate() { 73 if (!mShouldTestTelecom) { 74 return; 75 } 76 final Call conf = mInCallService.getLastConferenceCall(); 77 assertCallState(conf, Call.STATE_ACTIVE); 78 79 if (mCall1.getParent() != conf || mCall2.getParent() != conf) { 80 fail("The 2 participating calls should contain the conference call as its parent"); 81 } 82 if (!(conf.getChildren().contains(mCall1) && conf.getChildren().contains(mCall2))) { 83 fail("The conference call should contain the 2 participating calls as its children"); 84 } 85 assertTrue(mConferenceObject.getConnections().contains(mConnection1)); 86 87 assertConnectionState(mConferenceObject.getConnections().get(0), Connection.STATE_ACTIVE); 88 assertConnectionState(mConferenceObject.getConnections().get(1), Connection.STATE_ACTIVE); 89 assertConferenceState(mConferenceObject, Connection.STATE_ACTIVE); 90 } 91 92 public void testConferenceSplit() { 93 if (!mShouldTestTelecom) { 94 return; 95 } 96 final Call conf = mInCallService.getLastConferenceCall(); 97 assertCallState(conf, Call.STATE_ACTIVE); 98 99 if (!(mCall1.getParent() == conf) && (conf.getChildren().contains(mCall1))) { 100 fail("Call 1 not conferenced"); 101 } 102 assertTrue(mConferenceObject.getConnections().contains(mConnection1)); 103 104 splitFromConferenceCall(mCall1); 105 106 if ((mCall1.getParent() == conf) || (conf.getChildren().contains(mCall1))) { 107 fail("Call 1 should not be still conferenced"); 108 } 109 assertFalse(mConferenceObject.getConnections().contains(mConnection1)); 110 } 111 112 public void testConferenceHoldAndUnhold() { 113 if (!mShouldTestTelecom) { 114 return; 115 } 116 final Call conf = mInCallService.getLastConferenceCall(); 117 assertCallState(conf, Call.STATE_ACTIVE); 118 119 conf.hold(); 120 assertCallState(conf, Call.STATE_HOLDING); 121 assertCallState(mCall1, Call.STATE_HOLDING); 122 assertCallState(mCall2, Call.STATE_HOLDING); 123 124 conf.unhold(); 125 assertCallState(conf, Call.STATE_ACTIVE); 126 assertCallState(mCall1, Call.STATE_ACTIVE); 127 assertCallState(mCall2, Call.STATE_ACTIVE); 128 } 129 130 public void testConferenceMergeAndSwap() { 131 if (!mShouldTestTelecom) { 132 return; 133 } 134 final Call conf = mInCallService.getLastConferenceCall(); 135 assertCallState(conf, Call.STATE_ACTIVE); 136 137 conf.mergeConference(); 138 assertCallDisplayName(mCall1, TestUtils.MERGE_CALLER_NAME); 139 assertCallDisplayName(mCall2, TestUtils.MERGE_CALLER_NAME); 140 assertConnectionCallDisplayName(mConferenceObject.getConnections().get(0), 141 TestUtils.MERGE_CALLER_NAME); 142 assertConnectionCallDisplayName(mConferenceObject.getConnections().get(1), 143 TestUtils.MERGE_CALLER_NAME); 144 145 conf.swapConference(); 146 assertCallDisplayName(mCall1, TestUtils.SWAP_CALLER_NAME); 147 assertCallDisplayName(mCall2, TestUtils.SWAP_CALLER_NAME); 148 assertConnectionCallDisplayName(mConferenceObject.getConnections().get(0), 149 TestUtils.SWAP_CALLER_NAME); 150 assertConnectionCallDisplayName(mConferenceObject.getConnections().get(1), 151 TestUtils.SWAP_CALLER_NAME); 152 153 } 154 155 public void testConferenceSetters() { 156 if (!mShouldTestTelecom) { 157 return; 158 } 159 final Call conf = mInCallService.getLastConferenceCall(); 160 assertCallState(conf, Call.STATE_ACTIVE); 161 162 placeAndVerifyCall(); 163 MockConnection newConnection = verifyConnectionForOutgoingCall(2); 164 final Call newCall = mInCallService.getLastCall(); 165 166 ArrayList<Connection> connectionList = new ArrayList<>(); 167 connectionList.add(newConnection); 168 ArrayList<Call> callList = new ArrayList<>(); 169 callList.add(newCall); 170 171 assertFalse(conf.getDetails().can(Call.Details.CAPABILITY_MUTE)); 172 int capabilities = mConferenceObject.getConnectionCapabilities() | 173 Connection.CAPABILITY_MUTE; 174 mConferenceObject.setConnectionCapabilities(capabilities); 175 assertCallCapability(conf, Call.Details.CAPABILITY_MUTE); 176 177 assertFalse(conf.getConferenceableCalls().contains(newCall)); 178 mConferenceObject.setConferenceableConnections(connectionList); 179 assertCallConferenceableList(conf, callList); 180 181 // Consumed internally in Telecom; no verifiable manner to see the end point of this data 182 // through public APIs. 183 mConferenceObject.setConnectionTime(0); 184 mConferenceObject.setConnectionStartElapsedRealTime(0); 185 186 Bundle extras = new Bundle(); 187 extras.putString(TelecomManager.EXTRA_CALL_DISCONNECT_MESSAGE, "Test"); 188 assertFalse(conf.getDetails().getExtras().containsKey( 189 TelecomManager.EXTRA_CALL_DISCONNECT_MESSAGE)); 190 mConferenceObject.setExtras(extras); 191 assertCallExtras(conf, TelecomManager.EXTRA_CALL_DISCONNECT_MESSAGE, "Test"); 192 193 StatusHints hints = new StatusHints("Test", null, null); 194 assertNull(conf.getDetails().getStatusHints()); 195 mConferenceObject.setStatusHints(hints); 196 assertCallStatusHints(conf, hints); 197 198 assertFalse(conf.getChildren().contains(newCall)); 199 mConferenceObject.addConnection(newConnection); 200 assertCallChildrenContains(conf, newCall, true); 201 202 assertTrue(conf.getChildren().contains(newCall)); 203 mConferenceObject.removeConnection(newConnection); 204 assertCallChildrenContains(conf, newCall, false); 205 206 assertVideoState(conf, VideoProfile.STATE_AUDIO_ONLY); 207 final MockVideoProvider mockVideoProvider = mConnection1.getMockVideoProvider(); 208 mConferenceObject.setVideoProvider(mConnection1, mockVideoProvider); 209 mConferenceObject.setVideoState(mConnection1, VideoProfile.STATE_BIDIRECTIONAL); 210 assertVideoState(conf, VideoProfile.STATE_BIDIRECTIONAL); 211 212 // Dialing state is unsupported for conference calls. so, the state remains active. 213 mConferenceObject.setDialing(); 214 // just assert call state is not dialing, the state remains as previous one. 215 assertTrue(conf.getState() != Call.STATE_DIALING); 216 217 mConferenceObject.setOnHold(); 218 assertCallState(conf, Call.STATE_HOLDING); 219 220 mConferenceObject.setActive(); 221 assertCallState(conf, Call.STATE_ACTIVE); 222 223 mConferenceObject.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL)); 224 assertCallState(conf, Call.STATE_DISCONNECTED); 225 226 // Destroy state is unsupported for conference calls. so, the state remains active. 227 mConferenceObject.destroy(); 228 assertCallState(conf, Call.STATE_DISCONNECTED); 229 } 230 231 /** 232 * Tests end to end propagation of the {@link Conference} properties to the associated 233 * {@link Call}. 234 */ 235 public void testConferenceProperties() { 236 if (!mShouldTestTelecom) { 237 return; 238 } 239 final Call conf = mInCallService.getLastConferenceCall(); 240 assertCallState(conf, Call.STATE_ACTIVE); 241 242 int properties = mConferenceObject.getConnectionProperties(); 243 properties |= Connection.PROPERTY_HAS_CDMA_VOICE_PRIVACY; 244 245 mConferenceObject.setConnectionProperties(properties); 246 247 // Wait for 2nd properties change; the first will be when the conference is marked with 248 // Call.Details.PROPERTY_CONFERENCE. 249 assertCallProperties(conf, Call.Details.PROPERTY_HAS_CDMA_VOICE_PRIVACY); 250 assertTrue(conf.getDetails().hasProperty(Call.Details.PROPERTY_HAS_CDMA_VOICE_PRIVACY)); 251 } 252 253 /** 254 * Verifies {@link Conference#putExtras(Bundle)} calls are propagated to 255 * {@link android.telecom.Call.Callback#onDetailsChanged(Call, Call.Details)}. 256 */ 257 public void testConferencePutExtras() { 258 if (!mShouldTestTelecom) { 259 return; 260 } 261 final Call conf = mInCallService.getLastConferenceCall(); 262 assertCallState(conf, Call.STATE_ACTIVE); 263 264 Bundle extras = new Bundle(); 265 extras.putString(TEST_EXTRA_KEY_1, TEST_EXTRA_VALUE_1); 266 extras.putInt(TEST_EXTRA_KEY_2, TEST_EXTRA_VALUE_2); 267 int startInvokeCount = mOnExtrasChangedCounter.getInvokeCount(); 268 mConferenceObject.putExtras(extras); 269 mOnExtrasChangedCounter.waitForCount(startInvokeCount + 1, 270 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS); 271 272 Bundle changedExtras = conf.getDetails().getExtras(); 273 assertTrue(changedExtras.containsKey(TEST_EXTRA_KEY_1)); 274 assertTrue(changedExtras.containsKey(TEST_EXTRA_KEY_2)); 275 } 276 277 /** 278 * Verifies {@link Conference#removeExtras(List)} calls are propagated to 279 * {@link android.telecom.Call.Callback#onDetailsChanged(Call, Call.Details)}. 280 */ 281 public void testConferenceRemoveExtras() { 282 if (!mShouldTestTelecom) { 283 return; 284 } 285 final Call conf = mInCallService.getLastConferenceCall(); 286 assertCallState(conf, Call.STATE_ACTIVE); 287 288 setupExtras(); 289 290 int startInvokeCount = mOnExtrasChangedCounter.getInvokeCount(); 291 mConferenceObject.removeExtras(Arrays.asList(TEST_EXTRA_KEY_1)); 292 mOnExtrasChangedCounter.waitForCount(startInvokeCount + 1, 293 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS); 294 Bundle extras = mConferenceObject.getExtras(); 295 296 assertFalse(extras.containsKey(TEST_EXTRA_KEY_1)); 297 assertTrue(extras.containsKey(TEST_EXTRA_KEY_2)); 298 } 299 300 /** 301 * Verifies {@link Conference#removeExtras(String[])} calls are propagated to 302 * {@link android.telecom.Call.Callback#onDetailsChanged(Call, Call.Details)}. 303 */ 304 public void testConferenceRemoveExtras2() { 305 if (!mShouldTestTelecom) { 306 return; 307 } 308 final Call conf = mInCallService.getLastConferenceCall(); 309 assertCallState(conf, Call.STATE_ACTIVE); 310 311 setupExtras(); 312 313 int startInvokeCount = mOnExtrasChangedCounter.getInvokeCount(); 314 mConferenceObject.removeExtras(TEST_EXTRA_KEY_1, TEST_EXTRA_KEY_2); 315 mOnExtrasChangedCounter.waitForCount(startInvokeCount + 1, 316 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS); 317 Bundle extras = mConferenceObject.getExtras(); 318 319 assertFalse(extras.containsKey(TEST_EXTRA_KEY_1)); 320 assertFalse(extras.containsKey(TEST_EXTRA_KEY_2)); 321 } 322 323 private void setupExtras() { 324 Bundle extras = new Bundle(); 325 extras.putString(TEST_EXTRA_KEY_1, TEST_EXTRA_VALUE_1); 326 extras.putInt(TEST_EXTRA_KEY_2, TEST_EXTRA_VALUE_2); 327 int startInvokeCount = mOnExtrasChangedCounter.getInvokeCount(); 328 mConferenceObject.putExtras(extras); 329 mOnExtrasChangedCounter.waitForCount(startInvokeCount + 1, 330 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS); 331 } 332 333 /** 334 * Verifies {@link android.telecom.Call#putExtras(Bundle)} changes are propagated to 335 * {@link Conference#onExtrasChanged(Bundle)}. 336 */ 337 public void testConferenceOnExtraschanged() { 338 if (!mShouldTestTelecom) { 339 return; 340 } 341 final Call conf = mInCallService.getLastConferenceCall(); 342 assertCallState(conf, Call.STATE_ACTIVE); 343 344 Bundle extras = new Bundle(); 345 extras.putString(TEST_EXTRA_KEY_1, TEST_EXTRA_VALUE_1); 346 extras.putInt(TEST_EXTRA_KEY_2, TEST_EXTRA_VALUE_2); 347 conf.putExtras(extras); 348 mConferenceVerficationObject.mOnExtrasChanged.waitForCount(1); 349 350 Bundle changedExtras = mConferenceObject.getExtras(); 351 assertTrue(changedExtras.containsKey(TEST_EXTRA_KEY_1)); 352 assertTrue(changedExtras.containsKey(TEST_EXTRA_KEY_2)); 353 } 354 355 public void testConferenceAddAndRemoveConnection() { 356 if (!mShouldTestTelecom) { 357 return; 358 } 359 final Call conf = mInCallService.getLastConferenceCall(); 360 assertCallState(conf, Call.STATE_ACTIVE); 361 362 placeAndVerifyCall(); 363 MockConnection newConnection = verifyConnectionForOutgoingCall(2); 364 final Call newCall = mInCallService.getLastCall(); 365 366 ArrayList<Connection> connectionList = new ArrayList<>(); 367 connectionList.add(newConnection); 368 ArrayList<Call> callList = new ArrayList<>(); 369 callList.add(newCall); 370 371 assertFalse(conf.getChildren().contains(newCall)); 372 mConferenceObject.addConnection(newConnection); 373 assertCallChildrenContains(conf, newCall, true); 374 375 assertTrue(conf.getChildren().contains(newCall)); 376 mConferenceObject.removeConnection(newConnection); 377 assertCallChildrenContains(conf, newCall, false); 378 } 379 380 public void testConferenceDTMFTone() { 381 if (!mShouldTestTelecom) { 382 return; 383 } 384 final Call conf = mInCallService.getLastConferenceCall(); 385 assertCallState(conf, Call.STATE_ACTIVE); 386 387 assertTrue(((MockConference)mConferenceObject).getDtmfString().isEmpty()); 388 conf.playDtmfTone('1'); 389 assertDtmfString((MockConference)mConferenceObject, "1"); 390 conf.stopDtmfTone(); 391 assertDtmfString((MockConference)mConferenceObject, "1."); 392 conf.playDtmfTone('3'); 393 assertDtmfString((MockConference)mConferenceObject, "1.3"); 394 conf.stopDtmfTone(); 395 assertDtmfString((MockConference)mConferenceObject, "1.3."); 396 } 397 398 private void verifyConferenceObject(Conference mConferenceObject, MockConnection connection1, 399 MockConnection connection2) { 400 assertNull(mConferenceObject.getCallAudioState()); 401 assertTrue(mConferenceObject.getConferenceableConnections().isEmpty()); 402 assertEquals(connection1.getConnectionCapabilities(), 403 mConferenceObject.getConnectionCapabilities()); 404 assertEquals(connection1.getState(), mConferenceObject.getState()); 405 assertEquals(connection2.getState(), mConferenceObject.getState()); 406 assertTrue(mConferenceObject.getConnections().contains(connection1)); 407 assertTrue(mConferenceObject.getConnections().contains(connection2)); 408 assertEquals(connection1.getDisconnectCause(), mConferenceObject.getDisconnectCause()); 409 assertTrue(areBundlesEqual(connection1.getExtras(), mConferenceObject.getExtras())); 410 assertEquals(connection1.getPhoneAccountHandle(), mConferenceObject.getPhoneAccountHandle()); 411 assertEquals(connection1.getStatusHints(), mConferenceObject.getStatusHints()); 412 assertEquals(VideoProfile.STATE_AUDIO_ONLY, mConferenceObject.getVideoState()); 413 assertNull(mConferenceObject.getVideoProvider()); 414 } 415 416 private void addOutgoingCalls() { 417 try { 418 PhoneAccount account = setupConnectionService( 419 new MockConnectionService() { 420 @Override 421 public Connection onCreateOutgoingConnection( 422 PhoneAccountHandle connectionManagerPhoneAccount, 423 ConnectionRequest request) { 424 Connection connection = super.onCreateOutgoingConnection( 425 connectionManagerPhoneAccount, 426 request); 427 // Modify the connection object created with local values. 428 int capabilities = connection.getConnectionCapabilities(); 429 connection.setConnectionCapabilities(capabilities | CONF_CAPABILITIES); 430 return connection; 431 } 432 }, FLAG_REGISTER | FLAG_ENABLE); 433 } catch(Exception e) { 434 fail("Error in setting up the connection services"); 435 } 436 437 placeAndVerifyCall(); 438 mConnection1 = verifyConnectionForOutgoingCall(0); 439 mInCallService = mInCallCallbacks.getService(); 440 mCall1 = mInCallService.getLastCall(); 441 assertCallState(mCall1, Call.STATE_DIALING); 442 mConnection1.setActive(); 443 assertCallState(mCall1, Call.STATE_ACTIVE); 444 445 placeAndVerifyCall(); 446 mConnection2 = verifyConnectionForOutgoingCall(1); 447 mCall2 = mInCallService.getLastCall(); 448 assertCallState(mCall2, Call.STATE_DIALING); 449 mConnection2.setActive(); 450 assertCallState(mCall2, Call.STATE_ACTIVE); 451 452 setAndVerifyConferenceablesForOutgoingConnection(0); 453 setAndVerifyConferenceablesForOutgoingConnection(1); 454 } 455 456 private void assertCallCapability(final Call call, final int capability) { 457 waitUntilConditionIsTrueOrTimeout( 458 new Condition() { 459 @Override 460 public Object expected() { 461 return true; 462 } 463 464 @Override 465 public Object actual() { 466 return call.getDetails().can(capability); 467 } 468 }, 469 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 470 "Call should have capability " + capability 471 ); 472 } 473 474 private void assertCallConnectTime(final Call call, final int connectTime) { 475 waitUntilConditionIsTrueOrTimeout( 476 new Condition() { 477 @Override 478 public Object expected() { 479 return connectTime; 480 } 481 482 @Override 483 public Object actual() { 484 return call.getDetails().getConnectTimeMillis(); 485 } 486 }, 487 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 488 "Call should have connect time " + connectTime 489 ); 490 } 491 492 private void assertCallExtras(final Call call, final String key, final String value) { 493 waitUntilConditionIsTrueOrTimeout( 494 new Condition() { 495 @Override 496 public Object expected() { 497 return value; 498 } 499 500 @Override 501 public Object actual() { 502 return call.getDetails().getExtras() != null ? 503 call.getDetails().getExtras().getString(key) : null; 504 } 505 }, 506 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 507 "Call should have extra " + key + "=" + value 508 ); 509 } 510 511 private void assertCallStatusHints(final Call call, final StatusHints hints) { 512 waitUntilConditionIsTrueOrTimeout( 513 new Condition() { 514 @Override 515 public Object expected() { 516 return hints; 517 } 518 519 @Override 520 public Object actual() { 521 return call.getDetails().getStatusHints(); 522 } 523 }, 524 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 525 "Call should have status hints " + hints 526 ); 527 } 528 529 private void assertCallChildrenContains(final Call call, final Call childrenCall, 530 final boolean expected) { 531 waitUntilConditionIsTrueOrTimeout( 532 new Condition() { 533 @Override 534 public Object expected() { 535 return expected; 536 } 537 538 @Override 539 public Object actual() { 540 return call.getChildren().contains(childrenCall); 541 } 542 }, 543 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 544 expected == true ? "Call should have child call " + childrenCall : 545 "Call should not have child call " + childrenCall 546 ); 547 } 548 549 private void assertVideoState(final Call call, final int videoState) { 550 waitUntilConditionIsTrueOrTimeout( 551 new Condition() { 552 @Override 553 public Object expected() { 554 return videoState; 555 } 556 557 @Override 558 public Object actual() { 559 return call.getDetails().getVideoState(); 560 } 561 }, 562 TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 563 "Call should be in videoState " + videoState 564 ); 565 } 566 } 567