ConnectionServiceFixture.java revision b8b5a6976b44f92494971672ca3c1e6a71974d54
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(PhoneAccountHandle connectionManagerPhoneAccount, 244 String callId, ConnectionRequest request, boolean isIncoming, 245 Session.Info sessionInfo) throws RemoteException { 246 Log.i(ConnectionServiceFixture.this, "createConnectionFailed --> " + callId); 247 248 if (mConnectionById.containsKey(callId)) { 249 throw new RuntimeException("Connection already exists: " + callId); 250 } 251 252 // TODO(3p-calls): Implement this. 253 } 254 255 @Override 256 public void abort(String callId, Session.Info info) throws RemoteException { } 257 258 @Override 259 public void answerVideo(String callId, int videoState, 260 Session.Info info) throws RemoteException { } 261 262 @Override 263 public void answer(String callId, Session.Info info) throws RemoteException { } 264 265 @Override 266 public void reject(String callId, Session.Info info) throws RemoteException { 267 rejectedCallIds.add(callId); 268 } 269 270 @Override 271 public void rejectWithMessage(String callId, String message, 272 Session.Info info) throws RemoteException { } 273 274 @Override 275 public void disconnect(String callId, Session.Info info) throws RemoteException { } 276 277 @Override 278 public void silence(String callId, Session.Info info) throws RemoteException { } 279 280 @Override 281 public void hold(String callId, Session.Info info) throws RemoteException { } 282 283 @Override 284 public void unhold(String callId, Session.Info info) throws RemoteException { } 285 286 @Override 287 public void onCallAudioStateChanged(String activeCallId, CallAudioState audioState, 288 Session.Info info) 289 throws RemoteException { } 290 291 @Override 292 public void playDtmfTone(String callId, char digit, 293 Session.Info info) throws RemoteException { } 294 295 @Override 296 public void stopDtmfTone(String callId, Session.Info info) throws RemoteException { } 297 298 @Override 299 public void conference(String conferenceCallId, String callId, 300 Session.Info info) throws RemoteException { 301 mConnectionServiceDelegateAdapter.conference(conferenceCallId, callId, info); 302 } 303 304 @Override 305 public void splitFromConference(String callId, Session.Info info) throws RemoteException { } 306 307 @Override 308 public void mergeConference(String conferenceCallId, 309 Session.Info info) throws RemoteException { } 310 311 @Override 312 public void swapConference(String conferenceCallId, 313 Session.Info info) throws RemoteException { } 314 315 @Override 316 public void onPostDialContinue(String callId, boolean proceed, 317 Session.Info info) throws RemoteException { } 318 319 @Override 320 public void pullExternalCall(String callId, Session.Info info) throws RemoteException { } 321 322 @Override 323 public void sendCallEvent(String callId, String event, Bundle extras, 324 Session.Info info) throws RemoteException 325 {} 326 327 public void onExtrasChanged(String callId, Bundle extras, 328 Session.Info info) throws RemoteException { 329 mConnectionServiceDelegateAdapter.onExtrasChanged(callId, extras, info); 330 } 331 332 @Override 333 public void startRtt(String callId, ParcelFileDescriptor fromInCall, 334 ParcelFileDescriptor toInCall, Session.Info sessionInfo) throws RemoteException { 335 336 } 337 338 @Override 339 public void stopRtt(String callId, Session.Info sessionInfo) throws RemoteException { 340 341 } 342 343 @Override 344 public void respondToRttUpgradeRequest(String callId, ParcelFileDescriptor fromInCall, 345 ParcelFileDescriptor toInCall, Session.Info sessionInfo) throws RemoteException { 346 347 } 348 349 @Override 350 public IBinder asBinder() { 351 return this; 352 } 353 354 @Override 355 public IInterface queryLocalInterface(String descriptor) { 356 return this; 357 } 358 } 359 360 FakeConnectionServiceDelegate mConnectionServiceDelegate = 361 new FakeConnectionServiceDelegate(); 362 private IConnectionService mConnectionServiceDelegateAdapter = 363 IConnectionService.Stub.asInterface(mConnectionServiceDelegate.onBind(null)); 364 365 FakeConnectionService mConnectionService = new FakeConnectionService(); 366 private IConnectionService.Stub mConnectionServiceSpy = Mockito.spy(mConnectionService); 367 368 public class ConnectionInfo { 369 PhoneAccountHandle connectionManagerPhoneAccount; 370 String id; 371 boolean ringing; 372 ConnectionRequest request; 373 boolean isIncoming; 374 boolean isUnknown; 375 int state; 376 int addressPresentation; 377 int capabilities; 378 int properties; 379 int supportedAudioRoutes; 380 StatusHints statusHints; 381 DisconnectCause disconnectCause; 382 String conferenceId; 383 String callerDisplayName; 384 int callerDisplayNamePresentation; 385 final List<String> conferenceableConnectionIds = new ArrayList<>(); 386 IVideoProvider videoProvider; 387 Connection.VideoProvider videoProviderImpl; 388 MockVideoProvider mockVideoProvider; 389 int videoState; 390 boolean isVoipAudioMode; 391 Bundle extras; 392 boolean isConferenceCreated; 393 } 394 395 public class ConferenceInfo { 396 PhoneAccountHandle phoneAccount; 397 int state; 398 int capabilities; 399 int properties; 400 final List<String> connectionIds = new ArrayList<>(); 401 IVideoProvider videoProvider; 402 int videoState; 403 long connectTimeMillis; 404 StatusHints statusHints; 405 Bundle extras; 406 } 407 408 public String mLatestConnectionId; 409 public Connection mLatestConnection; 410 public Conference mLatestConference; 411 public final Set<IConnectionServiceAdapter> mConnectionServiceAdapters = new HashSet<>(); 412 public final Map<String, ConnectionInfo> mConnectionById = new HashMap<>(); 413 public final Map<String, ConferenceInfo> mConferenceById = new HashMap<>(); 414 public final List<ComponentName> mRemoteConnectionServiceNames = new ArrayList<>(); 415 public final List<IBinder> mRemoteConnectionServices = new ArrayList<>(); 416 417 public ConnectionServiceFixture() throws Exception { } 418 419 @Override 420 public IConnectionService getTestDouble() { 421 return mConnectionServiceSpy; 422 } 423 424 public void sendHandleCreateConnectionComplete(String id) throws Exception { 425 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 426 a.handleCreateConnectionComplete( 427 id, 428 mConnectionById.get(id).request, 429 parcelable(mConnectionById.get(id)), null /*Session.Info*/); 430 } 431 } 432 433 public void sendSetActive(String id) throws Exception { 434 mConnectionById.get(id).state = Connection.STATE_ACTIVE; 435 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 436 a.setActive(id, null /*Session.Info*/); 437 } 438 } 439 440 public void sendSetRinging(String id) throws Exception { 441 mConnectionById.get(id).state = Connection.STATE_RINGING; 442 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 443 a.setRinging(id, null /*Session.Info*/); 444 } 445 } 446 447 public void sendSetDialing(String id) throws Exception { 448 mConnectionById.get(id).state = Connection.STATE_DIALING; 449 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 450 a.setDialing(id, null /*Session.Info*/); 451 } 452 } 453 454 public void sendSetDisconnected(String id, int disconnectCause) throws Exception { 455 mConnectionById.get(id).state = Connection.STATE_DISCONNECTED; 456 mConnectionById.get(id).disconnectCause = new DisconnectCause(disconnectCause); 457 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 458 a.setDisconnected(id, mConnectionById.get(id).disconnectCause, null /*Session.Info*/); 459 } 460 } 461 462 public void sendSetOnHold(String id) throws Exception { 463 mConnectionById.get(id).state = Connection.STATE_HOLDING; 464 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 465 a.setOnHold(id, null /*Session.Info*/); 466 } 467 } 468 469 public void sendSetRingbackRequested(String id) throws Exception { 470 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 471 a.setRingbackRequested(id, mConnectionById.get(id).ringing, null /*Session.Info*/); 472 } 473 } 474 475 public void sendSetConnectionCapabilities(String id) throws Exception { 476 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 477 a.setConnectionCapabilities(id, mConnectionById.get(id).capabilities, 478 null /*Session.Info*/); 479 } 480 } 481 482 public void sendSetConnectionProperties(String id) throws Exception { 483 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 484 a.setConnectionProperties(id, mConnectionById.get(id).properties, null /*Session.Info*/); 485 } 486 } 487 public void sendSetIsConferenced(String id) throws Exception { 488 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 489 a.setIsConferenced(id, mConnectionById.get(id).conferenceId, null /*Session.Info*/); 490 } 491 } 492 493 public void sendAddConferenceCall(String id) throws Exception { 494 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 495 a.addConferenceCall(id, parcelable(mConferenceById.get(id)), null /*Session.Info*/); 496 } 497 } 498 499 public void sendRemoveCall(String id) throws Exception { 500 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 501 a.removeCall(id, null /*Session.Info*/); 502 } 503 } 504 505 public void sendOnPostDialWait(String id, String remaining) throws Exception { 506 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 507 a.onPostDialWait(id, remaining, null /*Session.Info*/); 508 } 509 } 510 511 public void sendOnPostDialChar(String id, char nextChar) throws Exception { 512 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 513 a.onPostDialChar(id, nextChar, null /*Session.Info*/); 514 } 515 } 516 517 public void sendQueryRemoteConnectionServices() throws Exception { 518 mRemoteConnectionServices.clear(); 519 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 520 a.queryRemoteConnectionServices(new RemoteServiceCallback.Stub() { 521 @Override 522 public void onError() throws RemoteException { 523 throw new RuntimeException(); 524 } 525 526 @Override 527 public void onResult( 528 List<ComponentName> names, 529 List<IBinder> services) 530 throws RemoteException { 531 TestCase.assertEquals(names.size(), services.size()); 532 mRemoteConnectionServiceNames.addAll(names); 533 mRemoteConnectionServices.addAll(services); 534 } 535 536 @Override 537 public IBinder asBinder() { 538 return this; 539 } 540 }, null /*Session.Info*/); 541 } 542 } 543 544 public void sendSetVideoProvider(String id) throws Exception { 545 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 546 a.setVideoProvider(id, mConnectionById.get(id).videoProvider, null /*Session.Info*/); 547 } 548 } 549 550 public void sendSetVideoState(String id) throws Exception { 551 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 552 a.setVideoState(id, mConnectionById.get(id).videoState, null /*Session.Info*/); 553 } 554 } 555 556 public void sendSetIsVoipAudioMode(String id) throws Exception { 557 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 558 a.setIsVoipAudioMode(id, mConnectionById.get(id).isVoipAudioMode, 559 null /*Session.Info*/); 560 } 561 } 562 563 public void sendSetStatusHints(String id) throws Exception { 564 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 565 a.setStatusHints(id, mConnectionById.get(id).statusHints, null /*Session.Info*/); 566 } 567 } 568 569 public void sendSetAddress(String id) throws Exception { 570 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 571 a.setAddress( 572 id, 573 mConnectionById.get(id).request.getAddress(), 574 mConnectionById.get(id).addressPresentation, null /*Session.Info*/); 575 } 576 } 577 578 public void sendSetCallerDisplayName(String id) throws Exception { 579 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 580 a.setCallerDisplayName( 581 id, 582 mConnectionById.get(id).callerDisplayName, 583 mConnectionById.get(id).callerDisplayNamePresentation, null /*Session.Info*/); 584 } 585 } 586 587 public void sendSetConferenceableConnections(String id) throws Exception { 588 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 589 a.setConferenceableConnections(id, mConnectionById.get(id).conferenceableConnectionIds, 590 null /*Session.Info*/); 591 } 592 } 593 594 public void sendAddExistingConnection(String id) throws Exception { 595 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 596 a.addExistingConnection(id, parcelable(mConnectionById.get(id)), null /*Session.Info*/); 597 } 598 } 599 600 public void sendConnectionEvent(String id, String event, Bundle extras) throws Exception { 601 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 602 a.onConnectionEvent(id, event, extras, null /*Session.Info*/); 603 } 604 } 605 606 public void sendSetConferenceMergeFailed(String id) throws Exception { 607 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 608 a.setConferenceMergeFailed(id, null /*Session.Info*/); 609 } 610 } 611 612 /** 613 * Waits until the {@link Connection#onExtrasChanged(Bundle)} API has been called on a 614 * {@link Connection} or {@link Conference}. 615 */ 616 public void waitForExtras() { 617 try { 618 mExtrasLock.await(TelecomSystemTest.TEST_TIMEOUT, TimeUnit.MILLISECONDS); 619 } catch (InterruptedException ie) { 620 } 621 mExtrasLock = new CountDownLatch(1); 622 } 623 624 private ParcelableConference parcelable(ConferenceInfo c) { 625 return new ParcelableConference( 626 c.phoneAccount, 627 c.state, 628 c.capabilities, 629 c.properties, 630 c.connectionIds, 631 c.videoProvider, 632 c.videoState, 633 c.connectTimeMillis, 634 c.statusHints, 635 c.extras); 636 } 637 638 private ParcelableConnection parcelable(ConnectionInfo c) { 639 return new ParcelableConnection( 640 c.request.getAccountHandle(), 641 c.state, 642 c.capabilities, 643 c.properties, 644 c.supportedAudioRoutes, 645 c.request.getAddress(), 646 c.addressPresentation, 647 c.callerDisplayName, 648 c.callerDisplayNamePresentation, 649 c.videoProvider, 650 c.videoState, 651 false, /* ringback requested */ 652 false, /* voip audio mode */ 653 0, /* Connect Time for conf call on this connection */ 654 c.statusHints, 655 c.disconnectCause, 656 c.conferenceableConnectionIds, 657 c.extras); 658 } 659} 660