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