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