ConnectionServiceFixture.java revision 115c06ee64e209cda99abdc1fbd23fd65aa6da47
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(PhoneAccountHandle connectionManagerPhoneAccount, 243 String callId, ConnectionRequest request, boolean isIncoming, 244 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 IBinder asBinder() { 333 return this; 334 } 335 336 @Override 337 public IInterface queryLocalInterface(String descriptor) { 338 return this; 339 } 340 } 341 342 FakeConnectionServiceDelegate mConnectionServiceDelegate = 343 new FakeConnectionServiceDelegate(); 344 private IConnectionService mConnectionServiceDelegateAdapter = 345 IConnectionService.Stub.asInterface(mConnectionServiceDelegate.onBind(null)); 346 347 FakeConnectionService mConnectionService = new FakeConnectionService(); 348 private IConnectionService.Stub mConnectionServiceSpy = Mockito.spy(mConnectionService); 349 350 public class ConnectionInfo { 351 PhoneAccountHandle connectionManagerPhoneAccount; 352 String id; 353 boolean ringing; 354 ConnectionRequest request; 355 boolean isIncoming; 356 boolean isUnknown; 357 int state; 358 int addressPresentation; 359 int capabilities; 360 int properties; 361 int supportedAudioRoutes; 362 StatusHints statusHints; 363 DisconnectCause disconnectCause; 364 String conferenceId; 365 String callerDisplayName; 366 int callerDisplayNamePresentation; 367 final List<String> conferenceableConnectionIds = new ArrayList<>(); 368 IVideoProvider videoProvider; 369 Connection.VideoProvider videoProviderImpl; 370 MockVideoProvider mockVideoProvider; 371 int videoState; 372 boolean isVoipAudioMode; 373 Bundle extras; 374 boolean isConferenceCreated; 375 } 376 377 public class ConferenceInfo { 378 PhoneAccountHandle phoneAccount; 379 int state; 380 int capabilities; 381 int properties; 382 final List<String> connectionIds = new ArrayList<>(); 383 IVideoProvider videoProvider; 384 int videoState; 385 long connectTimeMillis; 386 StatusHints statusHints; 387 Bundle extras; 388 } 389 390 public String mLatestConnectionId; 391 public Connection mLatestConnection; 392 public Conference mLatestConference; 393 public final Set<IConnectionServiceAdapter> mConnectionServiceAdapters = new HashSet<>(); 394 public final Map<String, ConnectionInfo> mConnectionById = new HashMap<>(); 395 public final Map<String, ConferenceInfo> mConferenceById = new HashMap<>(); 396 public final List<ComponentName> mRemoteConnectionServiceNames = new ArrayList<>(); 397 public final List<IBinder> mRemoteConnectionServices = new ArrayList<>(); 398 399 public ConnectionServiceFixture() throws Exception { } 400 401 @Override 402 public IConnectionService getTestDouble() { 403 return mConnectionServiceSpy; 404 } 405 406 public void sendHandleCreateConnectionComplete(String id) throws Exception { 407 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 408 a.handleCreateConnectionComplete( 409 id, 410 mConnectionById.get(id).request, 411 parcelable(mConnectionById.get(id)), null /*Session.Info*/); 412 } 413 } 414 415 public void sendSetActive(String id) throws Exception { 416 mConnectionById.get(id).state = Connection.STATE_ACTIVE; 417 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 418 a.setActive(id, null /*Session.Info*/); 419 } 420 } 421 422 public void sendSetRinging(String id) throws Exception { 423 mConnectionById.get(id).state = Connection.STATE_RINGING; 424 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 425 a.setRinging(id, null /*Session.Info*/); 426 } 427 } 428 429 public void sendSetDialing(String id) throws Exception { 430 mConnectionById.get(id).state = Connection.STATE_DIALING; 431 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 432 a.setDialing(id, null /*Session.Info*/); 433 } 434 } 435 436 public void sendSetDisconnected(String id, int disconnectCause) throws Exception { 437 mConnectionById.get(id).state = Connection.STATE_DISCONNECTED; 438 mConnectionById.get(id).disconnectCause = new DisconnectCause(disconnectCause); 439 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 440 a.setDisconnected(id, mConnectionById.get(id).disconnectCause, null /*Session.Info*/); 441 } 442 } 443 444 public void sendSetOnHold(String id) throws Exception { 445 mConnectionById.get(id).state = Connection.STATE_HOLDING; 446 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 447 a.setOnHold(id, null /*Session.Info*/); 448 } 449 } 450 451 public void sendSetRingbackRequested(String id) throws Exception { 452 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 453 a.setRingbackRequested(id, mConnectionById.get(id).ringing, null /*Session.Info*/); 454 } 455 } 456 457 public void sendSetConnectionCapabilities(String id) throws Exception { 458 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 459 a.setConnectionCapabilities(id, mConnectionById.get(id).capabilities, 460 null /*Session.Info*/); 461 } 462 } 463 464 public void sendSetConnectionProperties(String id) throws Exception { 465 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 466 a.setConnectionProperties(id, mConnectionById.get(id).properties, null /*Session.Info*/); 467 } 468 } 469 public void sendSetIsConferenced(String id) throws Exception { 470 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 471 a.setIsConferenced(id, mConnectionById.get(id).conferenceId, null /*Session.Info*/); 472 } 473 } 474 475 public void sendAddConferenceCall(String id) throws Exception { 476 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 477 a.addConferenceCall(id, parcelable(mConferenceById.get(id)), null /*Session.Info*/); 478 } 479 } 480 481 public void sendRemoveCall(String id) throws Exception { 482 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 483 a.removeCall(id, null /*Session.Info*/); 484 } 485 } 486 487 public void sendOnPostDialWait(String id, String remaining) throws Exception { 488 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 489 a.onPostDialWait(id, remaining, null /*Session.Info*/); 490 } 491 } 492 493 public void sendOnPostDialChar(String id, char nextChar) throws Exception { 494 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 495 a.onPostDialChar(id, nextChar, null /*Session.Info*/); 496 } 497 } 498 499 public void sendQueryRemoteConnectionServices() throws Exception { 500 mRemoteConnectionServices.clear(); 501 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 502 a.queryRemoteConnectionServices(new RemoteServiceCallback.Stub() { 503 @Override 504 public void onError() throws RemoteException { 505 throw new RuntimeException(); 506 } 507 508 @Override 509 public void onResult( 510 List<ComponentName> names, 511 List<IBinder> services) 512 throws RemoteException { 513 TestCase.assertEquals(names.size(), services.size()); 514 mRemoteConnectionServiceNames.addAll(names); 515 mRemoteConnectionServices.addAll(services); 516 } 517 518 @Override 519 public IBinder asBinder() { 520 return this; 521 } 522 }, null /*Session.Info*/); 523 } 524 } 525 526 public void sendSetVideoProvider(String id) throws Exception { 527 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 528 a.setVideoProvider(id, mConnectionById.get(id).videoProvider, null /*Session.Info*/); 529 } 530 } 531 532 public void sendSetVideoState(String id) throws Exception { 533 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 534 a.setVideoState(id, mConnectionById.get(id).videoState, null /*Session.Info*/); 535 } 536 } 537 538 public void sendSetIsVoipAudioMode(String id) throws Exception { 539 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 540 a.setIsVoipAudioMode(id, mConnectionById.get(id).isVoipAudioMode, 541 null /*Session.Info*/); 542 } 543 } 544 545 public void sendSetStatusHints(String id) throws Exception { 546 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 547 a.setStatusHints(id, mConnectionById.get(id).statusHints, null /*Session.Info*/); 548 } 549 } 550 551 public void sendSetAddress(String id) throws Exception { 552 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 553 a.setAddress( 554 id, 555 mConnectionById.get(id).request.getAddress(), 556 mConnectionById.get(id).addressPresentation, null /*Session.Info*/); 557 } 558 } 559 560 public void sendSetCallerDisplayName(String id) throws Exception { 561 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 562 a.setCallerDisplayName( 563 id, 564 mConnectionById.get(id).callerDisplayName, 565 mConnectionById.get(id).callerDisplayNamePresentation, null /*Session.Info*/); 566 } 567 } 568 569 public void sendSetConferenceableConnections(String id) throws Exception { 570 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 571 a.setConferenceableConnections(id, mConnectionById.get(id).conferenceableConnectionIds, 572 null /*Session.Info*/); 573 } 574 } 575 576 public void sendAddExistingConnection(String id) throws Exception { 577 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 578 a.addExistingConnection(id, parcelable(mConnectionById.get(id)), null /*Session.Info*/); 579 } 580 } 581 582 public void sendConnectionEvent(String id, String event, Bundle extras) throws Exception { 583 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 584 a.onConnectionEvent(id, event, extras, null /*Session.Info*/); 585 } 586 } 587 588 public void sendSetConferenceMergeFailed(String id) throws Exception { 589 for (IConnectionServiceAdapter a : mConnectionServiceAdapters) { 590 a.setConferenceMergeFailed(id, null /*Session.Info*/); 591 } 592 } 593 594 /** 595 * Waits until the {@link Connection#onExtrasChanged(Bundle)} API has been called on a 596 * {@link Connection} or {@link Conference}. 597 */ 598 public void waitForExtras() { 599 try { 600 mExtrasLock.await(TelecomSystemTest.TEST_TIMEOUT, TimeUnit.MILLISECONDS); 601 } catch (InterruptedException ie) { 602 } 603 mExtrasLock = new CountDownLatch(1); 604 } 605 606 private ParcelableConference parcelable(ConferenceInfo c) { 607 return new ParcelableConference( 608 c.phoneAccount, 609 c.state, 610 c.capabilities, 611 c.properties, 612 c.connectionIds, 613 c.videoProvider, 614 c.videoState, 615 c.connectTimeMillis, 616 c.statusHints, 617 c.extras); 618 } 619 620 private ParcelableConnection parcelable(ConnectionInfo c) { 621 return new ParcelableConnection( 622 c.request.getAccountHandle(), 623 c.state, 624 c.capabilities, 625 c.properties, 626 c.supportedAudioRoutes, 627 c.request.getAddress(), 628 c.addressPresentation, 629 c.callerDisplayName, 630 c.callerDisplayNamePresentation, 631 c.videoProvider, 632 c.videoState, 633 false, /* ringback requested */ 634 false, /* voip audio mode */ 635 0, /* Connect Time for conf call on this connection */ 636 c.statusHints, 637 c.disconnectCause, 638 c.conferenceableConnectionIds, 639 c.extras); 640 } 641} 642