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