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 com.android.server.telecom.tests; 18 19 import com.android.internal.telecom.IConnectionService; 20 import com.android.internal.telecom.IConnectionServiceAdapter; 21 import com.android.internal.telecom.IVideoProvider; 22 import com.android.internal.telecom.RemoteServiceCallback; 23 24 import junit.framework.TestCase; 25 26 import org.mockito.Mockito; 27 28 import android.content.ComponentName; 29 import android.net.Uri; 30 import android.os.Bundle; 31 import android.os.IBinder; 32 import android.os.IInterface; 33 import android.os.ParcelFileDescriptor; 34 import android.os.RemoteException; 35 import android.telecom.CallAudioState; 36 import android.telecom.Conference; 37 import android.telecom.Connection; 38 import android.telecom.ConnectionRequest; 39 import android.telecom.ConnectionService; 40 import android.telecom.DisconnectCause; 41 import android.telecom.Log; 42 import android.telecom.Logging.Session; 43 import android.telecom.ParcelableConference; 44 import android.telecom.ParcelableConnection; 45 import android.telecom.PhoneAccountHandle; 46 import android.telecom.StatusHints; 47 import android.telecom.TelecomManager; 48 49 import com.google.android.collect.Lists; 50 51 import java.lang.Override; 52 import java.util.ArrayList; 53 import java.util.Collection; 54 import java.util.HashMap; 55 import java.util.HashSet; 56 import java.util.List; 57 import java.util.Map; 58 import java.util.Set; 59 import java.util.concurrent.CountDownLatch; 60 import java.util.concurrent.TimeUnit; 61 62 /** 63 * Controls a test {@link IConnectionService} as would be provided by a source of connectivity 64 * to the Telecom framework. 65 */ 66 public class ConnectionServiceFixture implements TestFixture<IConnectionService> { 67 static int INVALID_VIDEO_STATE = -1; 68 public CountDownLatch mExtrasLock = new CountDownLatch(1); 69 static int NOT_SPECIFIED = 0; 70 71 /** 72 * Implementation of ConnectionService that performs no-ops for tasks normally meant for 73 * Telephony and reports success back to Telecom 74 */ 75 public class FakeConnectionServiceDelegate extends ConnectionService { 76 int mVideoState = INVALID_VIDEO_STATE; 77 int mCapabilities = NOT_SPECIFIED; 78 int mProperties = NOT_SPECIFIED; 79 80 @Override 81 public Connection onCreateUnknownConnection( 82 PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) { 83 mLatestConnection = new FakeConnection(request.getVideoState(), request.getAddress()); 84 return mLatestConnection; 85 } 86 87 @Override 88 public Connection onCreateIncomingConnection( 89 PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) { 90 FakeConnection fakeConnection = new FakeConnection( 91 mVideoState == INVALID_VIDEO_STATE ? request.getVideoState() : mVideoState, 92 request.getAddress()); 93 mLatestConnection = fakeConnection; 94 if (mCapabilities != NOT_SPECIFIED) { 95 fakeConnection.setConnectionCapabilities(mCapabilities); 96 } 97 if (mProperties != NOT_SPECIFIED) { 98 fakeConnection.setConnectionProperties(mProperties); 99 } 100 101 return fakeConnection; 102 } 103 104 @Override 105 public Connection onCreateOutgoingConnection( 106 PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) { 107 FakeConnection fakeConnection = new FakeConnection(request.getVideoState(), 108 request.getAddress()); 109 mLatestConnection = fakeConnection; 110 if (mCapabilities != NOT_SPECIFIED) { 111 fakeConnection.setConnectionCapabilities(mCapabilities); 112 } 113 if (mProperties != NOT_SPECIFIED) { 114 fakeConnection.setConnectionProperties(mProperties); 115 } 116 return fakeConnection; 117 } 118 119 @Override 120 public void onCreateConnectionComplete(Connection connection) { 121 } 122 123 @Override 124 public void onConference(Connection cxn1, Connection cxn2) { 125 if (((FakeConnection) cxn1).getIsConferenceCreated()) { 126 // Usually, this is implemented by something in Telephony, which does a bunch of 127 // radio work to conference the two connections together. Here we just short-cut 128 // that and declare them conferenced. 129 Conference fakeConference = new FakeConference(); 130 fakeConference.addConnection(cxn1); 131 fakeConference.addConnection(cxn2); 132 mLatestConference = fakeConference; 133 addConference(fakeConference); 134 } else { 135 try { 136 sendSetConferenceMergeFailed(cxn1.getTelecomCallId()); 137 } catch (Exception e) { 138 Log.w(this, "Exception on sendSetConferenceMergeFailed: " + e.getMessage()); 139 } 140 } 141 } 142 } 143 144 public class FakeConnection extends Connection { 145 // Set to false if you wish the Conference merge to fail. 146 boolean mIsConferenceCreated = true; 147 148 public FakeConnection(int videoState, Uri address) { 149 super(); 150 int capabilities = getConnectionCapabilities(); 151 capabilities |= CAPABILITY_MUTE; 152 capabilities |= CAPABILITY_SUPPORT_HOLD; 153 capabilities |= CAPABILITY_HOLD; 154 setVideoState(videoState); 155 setConnectionCapabilities(capabilities); 156 setDialing(); 157 setAddress(address, TelecomManager.PRESENTATION_ALLOWED); 158 } 159 160 @Override 161 public void onExtrasChanged(Bundle extras) { 162 mExtrasLock.countDown(); 163 } 164 165 public boolean getIsConferenceCreated() { 166 return mIsConferenceCreated; 167 } 168 169 public void setIsConferenceCreated(boolean isConferenceCreated) { 170 mIsConferenceCreated = isConferenceCreated; 171 } 172 } 173 174 public class FakeConference extends Conference { 175 public FakeConference() { 176 super(null); 177 setConnectionCapabilities( 178 Connection.CAPABILITY_SUPPORT_HOLD 179 | Connection.CAPABILITY_HOLD 180 | Connection.CAPABILITY_MUTE 181 | Connection.CAPABILITY_MANAGE_CONFERENCE); 182 } 183 184 @Override 185 public void onMerge(Connection connection) { 186 // Do nothing besides inform the connection that it was merged into this conference. 187 connection.setConference(this); 188 } 189 190 @Override 191 public void onExtrasChanged(Bundle extras) { 192 Log.w(this, "FakeConference onExtrasChanged"); 193 mExtrasLock.countDown(); 194 } 195 } 196 197 public class FakeConnectionService extends IConnectionService.Stub { 198 List<String> rejectedCallIds = Lists.newArrayList(); 199 200 @Override 201 public void addConnectionServiceAdapter(IConnectionServiceAdapter adapter, 202 Session.Info info) throws RemoteException { 203 if (!mConnectionServiceAdapters.add(adapter)) { 204 throw new RuntimeException("Adapter already added: " + adapter); 205 } 206 mConnectionServiceDelegateAdapter.addConnectionServiceAdapter(adapter, 207 null /*Session.Info*/); 208 } 209 210 @Override 211 public void removeConnectionServiceAdapter(IConnectionServiceAdapter adapter, 212 Session.Info info) throws RemoteException { 213 if (!mConnectionServiceAdapters.remove(adapter)) { 214 throw new RuntimeException("Adapter never added: " + adapter); 215 } 216 mConnectionServiceDelegateAdapter.removeConnectionServiceAdapter(adapter, 217 null /*Session.Info*/); 218 } 219 220 @Override 221 public void createConnection(PhoneAccountHandle connectionManagerPhoneAccount, 222 String id, ConnectionRequest request, boolean isIncoming, boolean isUnknown, 223 Session.Info info) throws RemoteException { 224 Log.i(ConnectionServiceFixture.this, "createConnection --> " + id); 225 226 if (mConnectionById.containsKey(id)) { 227 throw new RuntimeException("Connection already exists: " + id); 228 } 229 mLatestConnectionId = id; 230 ConnectionInfo c = new ConnectionInfo(); 231 c.connectionManagerPhoneAccount = connectionManagerPhoneAccount; 232 c.id = id; 233 c.request = request; 234 c.isIncoming = isIncoming; 235 c.isUnknown = isUnknown; 236 c.capabilities |= Connection.CAPABILITY_HOLD | Connection.CAPABILITY_SUPPORT_HOLD; 237 c.videoState = request.getVideoState(); 238 c.mockVideoProvider = new MockVideoProvider(); 239 c.videoProvider = c.mockVideoProvider.getInterface(); 240 c.isConferenceCreated = true; 241 mConnectionById.put(id, c); 242 mConnectionServiceDelegateAdapter.createConnection(connectionManagerPhoneAccount, 243 id, request, isIncoming, isUnknown, null /*Session.Info*/); 244 } 245 246 @Override 247 public void createConnectionComplete(String id, Session.Info info) throws RemoteException { 248 mConnectionServiceDelegateAdapter.createConnectionComplete(id, null /*Session.Info*/); 249 } 250 251 @Override 252 public void createConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount, 253 String callId, ConnectionRequest request, boolean isIncoming, 254 Session.Info sessionInfo) throws RemoteException { 255 Log.i(ConnectionServiceFixture.this, "createConnectionFailed --> " + callId); 256 257 if (mConnectionById.containsKey(callId)) { 258 throw new RuntimeException("Connection already exists: " + callId); 259 } 260 261 // TODO(3p-calls): Implement this. 262 } 263 264 @Override 265 public void abort(String callId, Session.Info info) throws RemoteException { } 266 267 @Override 268 public void answerVideo(String callId, int videoState, 269 Session.Info info) throws RemoteException { } 270 271 @Override 272 public void answer(String callId, Session.Info info) throws RemoteException { } 273 274 @Override 275 public void reject(String callId, Session.Info info) throws RemoteException { 276 rejectedCallIds.add(callId); 277 } 278 279 @Override 280 public void rejectWithMessage(String callId, String message, 281 Session.Info info) throws RemoteException { } 282 283 @Override 284 public void disconnect(String callId, Session.Info info) throws RemoteException { } 285 286 @Override 287 public void silence(String callId, Session.Info info) throws RemoteException { } 288 289 @Override 290 public void hold(String callId, Session.Info info) throws RemoteException { } 291 292 @Override 293 public void unhold(String callId, Session.Info info) throws RemoteException { } 294 295 @Override 296 public void onCallAudioStateChanged(String activeCallId, CallAudioState audioState, 297 Session.Info info) 298 throws RemoteException { } 299 300 @Override 301 public void playDtmfTone(String callId, char digit, 302 Session.Info info) throws RemoteException { } 303 304 @Override 305 public void stopDtmfTone(String callId, Session.Info info) throws RemoteException { } 306 307 @Override 308 public void conference(String conferenceCallId, String callId, 309 Session.Info info) throws RemoteException { 310 mConnectionServiceDelegateAdapter.conference(conferenceCallId, callId, info); 311 } 312 313 @Override 314 public void splitFromConference(String callId, Session.Info info) throws RemoteException { } 315 316 @Override 317 public void mergeConference(String conferenceCallId, 318 Session.Info info) throws RemoteException { } 319 320 @Override 321 public void swapConference(String conferenceCallId, 322 Session.Info info) throws RemoteException { } 323 324 @Override 325 public void onPostDialContinue(String callId, boolean proceed, 326 Session.Info info) throws RemoteException { } 327 328 @Override 329 public void pullExternalCall(String callId, Session.Info info) throws RemoteException { } 330 331 @Override 332 public void sendCallEvent(String callId, String event, Bundle extras, 333 Session.Info info) throws RemoteException 334 {} 335 336 public void onExtrasChanged(String callId, Bundle extras, 337 Session.Info info) throws RemoteException { 338 mConnectionServiceDelegateAdapter.onExtrasChanged(callId, extras, info); 339 } 340 341 @Override 342 public void startRtt(String callId, ParcelFileDescriptor fromInCall, 343 ParcelFileDescriptor toInCall, Session.Info sessionInfo) throws RemoteException { 344 345 } 346 347 @Override 348 public void stopRtt(String callId, Session.Info sessionInfo) throws RemoteException { 349 350 } 351 352 @Override 353 public void respondToRttUpgradeRequest(String callId, ParcelFileDescriptor fromInCall, 354 ParcelFileDescriptor toInCall, Session.Info sessionInfo) throws RemoteException { 355 356 } 357 358 @Override 359 public IBinder asBinder() { 360 return this; 361 } 362 363 @Override 364 public IInterface queryLocalInterface(String descriptor) { 365 return this; 366 } 367 } 368 369 FakeConnectionServiceDelegate mConnectionServiceDelegate = 370 new FakeConnectionServiceDelegate(); 371 private IConnectionService mConnectionServiceDelegateAdapter = 372 IConnectionService.Stub.asInterface(mConnectionServiceDelegate.onBind(null)); 373 374 FakeConnectionService mConnectionService = new FakeConnectionService(); 375 private IConnectionService.Stub mConnectionServiceSpy = Mockito.spy(mConnectionService); 376 377 public class ConnectionInfo { 378 PhoneAccountHandle connectionManagerPhoneAccount; 379 String id; 380 boolean ringing; 381 ConnectionRequest request; 382 boolean isIncoming; 383 boolean isUnknown; 384 int state; 385 int addressPresentation; 386 int capabilities; 387 int properties; 388 int supportedAudioRoutes; 389 StatusHints statusHints; 390 DisconnectCause disconnectCause; 391 String conferenceId; 392 String callerDisplayName; 393 int callerDisplayNamePresentation; 394 final List<String> conferenceableConnectionIds = new ArrayList<>(); 395 IVideoProvider videoProvider; 396 Connection.VideoProvider videoProviderImpl; 397 MockVideoProvider mockVideoProvider; 398 int videoState; 399 boolean isVoipAudioMode; 400 Bundle extras; 401 boolean isConferenceCreated; 402 } 403 404 public class ConferenceInfo { 405 PhoneAccountHandle phoneAccount; 406 int state; 407 int capabilities; 408 int properties; 409 final List<String> connectionIds = new ArrayList<>(); 410 IVideoProvider videoProvider; 411 int videoState; 412 long connectTimeMillis; 413 StatusHints statusHints; 414 Bundle extras; 415 } 416 417 public String mLatestConnectionId; 418 public Connection mLatestConnection; 419 public Conference mLatestConference; 420 public final Set<IConnectionServiceAdapter> mConnectionServiceAdapters = new HashSet<>(); 421 public final Map<String, ConnectionInfo> mConnectionById = new HashMap<>(); 422 public final Map<String, ConferenceInfo> mConferenceById = new HashMap<>(); 423 public final List<ComponentName> mRemoteConnectionServiceNames = new ArrayList<>(); 424 public final List<IBinder> mRemoteConnectionServices = new ArrayList<>(); 425 426 public ConnectionServiceFixture() throws Exception { } 427 428 @Override 429 public IConnectionService getTestDouble() { 430 return mConnectionServiceSpy; 431 } 432 433 public void sendHandleCreateConnectionComplete(String id) throws Exception { 434 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 435 a.handleCreateConnectionComplete( 436 id, 437 mConnectionById.get(id).request, 438 parcelable(mConnectionById.get(id)), null /*Session.Info*/); 439 } 440 } 441 442 public void sendSetActive(String id) throws Exception { 443 mConnectionById.get(id).state = Connection.STATE_ACTIVE; 444 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 445 a.setActive(id, null /*Session.Info*/); 446 } 447 } 448 449 public void sendSetRinging(String id) throws Exception { 450 mConnectionById.get(id).state = Connection.STATE_RINGING; 451 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 452 a.setRinging(id, null /*Session.Info*/); 453 } 454 } 455 456 public void sendSetDialing(String id) throws Exception { 457 mConnectionById.get(id).state = Connection.STATE_DIALING; 458 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 459 a.setDialing(id, null /*Session.Info*/); 460 } 461 } 462 463 public void sendSetDisconnected(String id, int disconnectCause) throws Exception { 464 mConnectionById.get(id).state = Connection.STATE_DISCONNECTED; 465 mConnectionById.get(id).disconnectCause = new DisconnectCause(disconnectCause); 466 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 467 a.setDisconnected(id, mConnectionById.get(id).disconnectCause, null /*Session.Info*/); 468 } 469 } 470 471 public void sendSetOnHold(String id) throws Exception { 472 mConnectionById.get(id).state = Connection.STATE_HOLDING; 473 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 474 a.setOnHold(id, null /*Session.Info*/); 475 } 476 } 477 478 public void sendSetRingbackRequested(String id) throws Exception { 479 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 480 a.setRingbackRequested(id, mConnectionById.get(id).ringing, null /*Session.Info*/); 481 } 482 } 483 484 public void sendSetConnectionCapabilities(String id) throws Exception { 485 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 486 a.setConnectionCapabilities(id, mConnectionById.get(id).capabilities, 487 null /*Session.Info*/); 488 } 489 } 490 491 public void sendSetConnectionProperties(String id) throws Exception { 492 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 493 a.setConnectionProperties(id, mConnectionById.get(id).properties, null /*Session.Info*/); 494 } 495 } 496 public void sendSetIsConferenced(String id) throws Exception { 497 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 498 a.setIsConferenced(id, mConnectionById.get(id).conferenceId, null /*Session.Info*/); 499 } 500 } 501 502 public void sendAddConferenceCall(String id) throws Exception { 503 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 504 a.addConferenceCall(id, parcelable(mConferenceById.get(id)), null /*Session.Info*/); 505 } 506 } 507 508 public void sendRemoveCall(String id) throws Exception { 509 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 510 a.removeCall(id, null /*Session.Info*/); 511 } 512 } 513 514 public void sendOnPostDialWait(String id, String remaining) throws Exception { 515 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 516 a.onPostDialWait(id, remaining, null /*Session.Info*/); 517 } 518 } 519 520 public void sendOnPostDialChar(String id, char nextChar) throws Exception { 521 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 522 a.onPostDialChar(id, nextChar, null /*Session.Info*/); 523 } 524 } 525 526 public void sendQueryRemoteConnectionServices() throws Exception { 527 mRemoteConnectionServices.clear(); 528 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 529 a.queryRemoteConnectionServices(new RemoteServiceCallback.Stub() { 530 @Override 531 public void onError() throws RemoteException { 532 throw new RuntimeException(); 533 } 534 535 @Override 536 public void onResult( 537 List<ComponentName> names, 538 List<IBinder> services) 539 throws RemoteException { 540 TestCase.assertEquals(names.size(), services.size()); 541 mRemoteConnectionServiceNames.addAll(names); 542 mRemoteConnectionServices.addAll(services); 543 } 544 545 @Override 546 public IBinder asBinder() { 547 return this; 548 } 549 }, null /*Session.Info*/); 550 } 551 } 552 553 public void sendSetVideoProvider(String id) throws Exception { 554 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 555 a.setVideoProvider(id, mConnectionById.get(id).videoProvider, null /*Session.Info*/); 556 } 557 } 558 559 public void sendSetVideoState(String id) throws Exception { 560 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 561 a.setVideoState(id, mConnectionById.get(id).videoState, null /*Session.Info*/); 562 } 563 } 564 565 public void sendSetIsVoipAudioMode(String id) throws Exception { 566 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 567 a.setIsVoipAudioMode(id, mConnectionById.get(id).isVoipAudioMode, 568 null /*Session.Info*/); 569 } 570 } 571 572 public void sendSetStatusHints(String id) throws Exception { 573 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 574 a.setStatusHints(id, mConnectionById.get(id).statusHints, null /*Session.Info*/); 575 } 576 } 577 578 public void sendSetAddress(String id) throws Exception { 579 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 580 a.setAddress( 581 id, 582 mConnectionById.get(id).request.getAddress(), 583 mConnectionById.get(id).addressPresentation, null /*Session.Info*/); 584 } 585 } 586 587 public void sendSetCallerDisplayName(String id) throws Exception { 588 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 589 a.setCallerDisplayName( 590 id, 591 mConnectionById.get(id).callerDisplayName, 592 mConnectionById.get(id).callerDisplayNamePresentation, null /*Session.Info*/); 593 } 594 } 595 596 public void sendSetConferenceableConnections(String id) throws Exception { 597 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 598 a.setConferenceableConnections(id, mConnectionById.get(id).conferenceableConnectionIds, 599 null /*Session.Info*/); 600 } 601 } 602 603 public void sendAddExistingConnection(String id) throws Exception { 604 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 605 a.addExistingConnection(id, parcelable(mConnectionById.get(id)), null /*Session.Info*/); 606 } 607 } 608 609 public void sendConnectionEvent(String id, String event, Bundle extras) throws Exception { 610 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 611 a.onConnectionEvent(id, event, extras, null /*Session.Info*/); 612 } 613 } 614 615 public void sendSetConferenceMergeFailed(String id) throws Exception { 616 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 617 a.setConferenceMergeFailed(id, null /*Session.Info*/); 618 } 619 } 620 621 /** 622 * Waits until the {@link Connection#onExtrasChanged(Bundle)} API has been called on a 623 * {@link Connection} or {@link Conference}. 624 */ 625 public void waitForExtras() { 626 try { 627 mExtrasLock.await(TelecomSystemTest.TEST_TIMEOUT, TimeUnit.MILLISECONDS); 628 } catch (InterruptedException ie) { 629 } 630 mExtrasLock = new CountDownLatch(1); 631 } 632 633 private ParcelableConference parcelable(ConferenceInfo c) { 634 return new ParcelableConference( 635 c.phoneAccount, 636 c.state, 637 c.capabilities, 638 c.properties, 639 c.connectionIds, 640 c.videoProvider, 641 c.videoState, 642 c.connectTimeMillis, 643 c.statusHints, 644 c.extras); 645 } 646 647 private ParcelableConnection parcelable(ConnectionInfo c) { 648 return new ParcelableConnection( 649 c.request.getAccountHandle(), 650 c.state, 651 c.capabilities, 652 c.properties, 653 c.supportedAudioRoutes, 654 c.request.getAddress(), 655 c.addressPresentation, 656 c.callerDisplayName, 657 c.callerDisplayNamePresentation, 658 c.videoProvider, 659 c.videoState, 660 false, /* ringback requested */ 661 false, /* voip audio mode */ 662 0, /* Connect Time for conf call on this connection */ 663 c.statusHints, 664 c.disconnectCause, 665 c.conferenceableConnectionIds, 666 c.extras); 667 } 668 } 669