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