blob: 6e6646f7aef2b3d8d472b0b36e06b0ff0271d2be [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.telecom.tests;
import com.android.internal.telecom.IConnectionService;
import com.android.internal.telecom.IConnectionServiceAdapter;
import com.android.internal.telecom.IVideoProvider;
import com.android.internal.telecom.RemoteServiceCallback;
import junit.framework.TestCase;
import org.mockito.Mockito;
import android.content.ComponentName;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IInterface;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.telecom.CallAudioState;
import android.telecom.CallScreeningService;
import android.telecom.Conference;
import android.telecom.Connection;
import android.telecom.ConnectionRequest;
import android.telecom.ConnectionService;
import android.telecom.DisconnectCause;
import android.telecom.Log;
import android.telecom.Logging.Session;
import android.telecom.ParcelableConference;
import android.telecom.ParcelableConnection;
import android.telecom.PhoneAccountHandle;
import android.telecom.StatusHints;
import android.telecom.TelecomManager;
import com.google.android.collect.Lists;
import java.lang.Override;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Controls a test {@link IConnectionService} as would be provided by a source of connectivity
* to the Telecom framework.
*/
public class ConnectionServiceFixture implements TestFixture<IConnectionService> {
static int INVALID_VIDEO_STATE = -1;
public CountDownLatch mExtrasLock = new CountDownLatch(1);
static int NOT_SPECIFIED = 0;
/**
* Implementation of ConnectionService that performs no-ops for tasks normally meant for
* Telephony and reports success back to Telecom
*/
public class FakeConnectionServiceDelegate extends ConnectionService {
int mVideoState = INVALID_VIDEO_STATE;
int mCapabilities = NOT_SPECIFIED;
int mProperties = NOT_SPECIFIED;
public FakeConnectionServiceDelegate(Context base) {
attachBaseContext(base);
}
@Override
public Connection onCreateUnknownConnection(
PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
mLatestConnection = new FakeConnection(request.getVideoState(), request.getAddress());
return mLatestConnection;
}
@Override
public Connection onCreateIncomingConnection(
PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
FakeConnection fakeConnection = new FakeConnection(
mVideoState == INVALID_VIDEO_STATE ? request.getVideoState() : mVideoState,
request.getAddress());
mLatestConnection = fakeConnection;
if (mCapabilities != NOT_SPECIFIED) {
fakeConnection.setConnectionCapabilities(mCapabilities);
}
if (mProperties != NOT_SPECIFIED) {
fakeConnection.setConnectionProperties(mProperties);
}
return fakeConnection;
}
@Override
public Connection onCreateOutgoingConnection(
PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
FakeConnection fakeConnection = new FakeConnection(request.getVideoState(),
request.getAddress());
mLatestConnection = fakeConnection;
if (mCapabilities != NOT_SPECIFIED) {
fakeConnection.setConnectionCapabilities(mCapabilities);
}
if (mProperties != NOT_SPECIFIED) {
fakeConnection.setConnectionProperties(mProperties);
}
return fakeConnection;
}
@Override
public void onCreateConnectionComplete(Connection connection) {
}
@Override
public void onConference(Connection cxn1, Connection cxn2) {
if (((FakeConnection) cxn1).getIsConferenceCreated()) {
// Usually, this is implemented by something in Telephony, which does a bunch of
// radio work to conference the two connections together. Here we just short-cut
// that and declare them conferenced.
Conference fakeConference = new FakeConference();
fakeConference.addConnection(cxn1);
fakeConference.addConnection(cxn2);
mLatestConference = fakeConference;
addConference(fakeConference);
} else {
try {
sendSetConferenceMergeFailed(cxn1.getTelecomCallId());
} catch (Exception e) {
Log.w(this, "Exception on sendSetConferenceMergeFailed: " + e.getMessage());
}
}
}
}
public class FakeConnection extends Connection {
// Set to false if you wish the Conference merge to fail.
boolean mIsConferenceCreated = true;
public FakeConnection(int videoState, Uri address) {
super();
int capabilities = getConnectionCapabilities();
capabilities |= CAPABILITY_MUTE;
capabilities |= CAPABILITY_SUPPORT_HOLD;
capabilities |= CAPABILITY_HOLD;
setVideoState(videoState);
setConnectionCapabilities(capabilities);
setDialing();
setAddress(address, TelecomManager.PRESENTATION_ALLOWED);
}
@Override
public void onExtrasChanged(Bundle extras) {
mExtrasLock.countDown();
}
public boolean getIsConferenceCreated() {
return mIsConferenceCreated;
}
public void setIsConferenceCreated(boolean isConferenceCreated) {
mIsConferenceCreated = isConferenceCreated;
}
}
public class FakeConference extends Conference {
public FakeConference() {
super(null);
setConnectionCapabilities(
Connection.CAPABILITY_SUPPORT_HOLD
| Connection.CAPABILITY_HOLD
| Connection.CAPABILITY_MUTE
| Connection.CAPABILITY_MANAGE_CONFERENCE);
}
@Override
public void onMerge(Connection connection) {
// Do nothing besides inform the connection that it was merged into this conference.
connection.setConference(this);
}
@Override
public void onExtrasChanged(Bundle extras) {
Log.w(this, "FakeConference onExtrasChanged");
mExtrasLock.countDown();
}
}
public class FakeConnectionService extends IConnectionService.Stub {
List<String> rejectedCallIds = Lists.newArrayList();
@Override
public void addConnectionServiceAdapter(IConnectionServiceAdapter adapter,
Session.Info info) throws RemoteException {
if (!mConnectionServiceAdapters.add(adapter)) {
throw new RuntimeException("Adapter already added: " + adapter);
}
mConnectionServiceDelegateAdapter.addConnectionServiceAdapter(adapter,
null /*Session.Info*/);
}
@Override
public void removeConnectionServiceAdapter(IConnectionServiceAdapter adapter,
Session.Info info) throws RemoteException {
if (!mConnectionServiceAdapters.remove(adapter)) {
throw new RuntimeException("Adapter never added: " + adapter);
}
mConnectionServiceDelegateAdapter.removeConnectionServiceAdapter(adapter,
null /*Session.Info*/);
}
@Override
public void createConnection(PhoneAccountHandle connectionManagerPhoneAccount,
String id, ConnectionRequest request, boolean isIncoming, boolean isUnknown,
Session.Info info) throws RemoteException {
Log.i(ConnectionServiceFixture.this, "createConnection --> " + id);
if (mConnectionById.containsKey(id)) {
throw new RuntimeException("Connection already exists: " + id);
}
mLatestConnectionId = id;
ConnectionInfo c = new ConnectionInfo();
c.connectionManagerPhoneAccount = connectionManagerPhoneAccount;
c.id = id;
c.request = request;
c.isIncoming = isIncoming;
c.isUnknown = isUnknown;
c.capabilities |= Connection.CAPABILITY_HOLD | Connection.CAPABILITY_SUPPORT_HOLD;
c.videoState = request.getVideoState();
c.mockVideoProvider = new MockVideoProvider();
c.videoProvider = c.mockVideoProvider.getInterface();
c.isConferenceCreated = true;
mConnectionById.put(id, c);
mConnectionServiceDelegateAdapter.createConnection(connectionManagerPhoneAccount,
id, request, isIncoming, isUnknown, null /*Session.Info*/);
}
@Override
public void createConnectionComplete(String id, Session.Info info) throws RemoteException {
Log.i(ConnectionServiceFixture.this, "createConnectionComplete: %s", id);
mConnectionServiceDelegateAdapter.createConnectionComplete(id, null /*Session.Info*/);
}
@Override
public void createConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount,
String callId, ConnectionRequest request, boolean isIncoming,
Session.Info sessionInfo) throws RemoteException {
Log.i(ConnectionServiceFixture.this, "createConnectionFailed --> " + callId);
if (mConnectionById.containsKey(callId)) {
throw new RuntimeException("Connection already exists: " + callId);
}
// TODO(3p-calls): Implement this.
}
@Override
public void createConference(
PhoneAccountHandle connectionManagerPhoneAccount,
String id,
ConnectionRequest request,
boolean isIncoming,
boolean isUnknown,
Session.Info sessionInfo) { }
@Override
public void createConferenceComplete(String id, Session.Info sessionInfo) { }
@Override
public void createConferenceFailed(
PhoneAccountHandle connectionManagerPhoneAccount,
String callId,
ConnectionRequest request,
boolean isIncoming,
Session.Info sessionInfo) { }
@Override
public void abort(String callId, Session.Info info) throws RemoteException { }
@Override
public void answerVideo(String callId, int videoState,
Session.Info info) throws RemoteException { }
@Override
public void answer(String callId, Session.Info info) throws RemoteException { }
@Override
public void deflect(String callId, Uri address, Session.Info info)
throws RemoteException { }
@Override
public void transfer(String callId, Uri number, boolean isConfirmationRequired,
Session.Info info) throws RemoteException { }
@Override
public void consultativeTransfer(String callId, String otherCallId,
Session.Info info) throws RemoteException { }
@Override
public void reject(String callId, Session.Info info) throws RemoteException {
rejectedCallIds.add(callId);
}
@Override public void rejectWithReason(java.lang.String callId, int rejectReason,
android.telecom.Logging.Session.Info sessionInfo) throws RemoteException {
rejectedCallIds.add(callId);
}
@Override
public void rejectWithMessage(String callId, String message,
Session.Info info) throws RemoteException {
rejectedCallIds.add(callId);
}
@Override
public void disconnect(String callId, Session.Info info) throws RemoteException { }
@Override
public void silence(String callId, Session.Info info) throws RemoteException { }
@Override
public void hold(String callId, Session.Info info) throws RemoteException { }
@Override
public void unhold(String callId, Session.Info info) throws RemoteException { }
@Override
public void onCallAudioStateChanged(String activeCallId, CallAudioState audioState,
Session.Info info)
throws RemoteException { }
@Override
public void onUsingAlternativeUi(String activeCallId, boolean usingAlternativeUi,
Session.Info info) throws RemoteException { }
@Override
public void onTrackedByNonUiService(String activeCallId, boolean isTracked,
Session.Info info) throws RemoteException { }
@Override
public void playDtmfTone(String callId, char digit,
Session.Info info) throws RemoteException { }
@Override
public void stopDtmfTone(String callId, Session.Info info) throws RemoteException { }
@Override
public void conference(String conferenceCallId, String callId,
Session.Info info) throws RemoteException {
mConnectionServiceDelegateAdapter.conference(conferenceCallId, callId, info);
}
@Override
public void splitFromConference(String callId, Session.Info info) throws RemoteException { }
@Override
public void mergeConference(String conferenceCallId,
Session.Info info) throws RemoteException { }
@Override
public void swapConference(String conferenceCallId,
Session.Info info) throws RemoteException { }
@Override
public void addConferenceParticipants(String CallId, List<Uri> participants,
Session.Info sessionInfo) throws RemoteException {
}
@Override
public void onPostDialContinue(String callId, boolean proceed,
Session.Info info) throws RemoteException { }
@Override
public void pullExternalCall(String callId, Session.Info info) throws RemoteException { }
@Override
public void sendCallEvent(String callId, String event, Bundle extras,
Session.Info info) throws RemoteException
{}
public void onExtrasChanged(String callId, Bundle extras,
Session.Info info) throws RemoteException {
mConnectionServiceDelegateAdapter.onExtrasChanged(callId, extras, info);
}
@Override
public void startRtt(String callId, ParcelFileDescriptor fromInCall,
ParcelFileDescriptor toInCall, Session.Info sessionInfo) throws RemoteException {
}
@Override
public void stopRtt(String callId, Session.Info sessionInfo) throws RemoteException {
}
@Override
public void respondToRttUpgradeRequest(String callId, ParcelFileDescriptor fromInCall,
ParcelFileDescriptor toInCall, Session.Info sessionInfo) throws RemoteException {
}
@Override
public void connectionServiceFocusLost(Session.Info sessionInfo) throws RemoteException {
}
@Override
public void connectionServiceFocusGained(Session.Info sessionInfo) throws RemoteException {
}
@Override
public IBinder asBinder() {
return this;
}
@Override
public IInterface queryLocalInterface(String descriptor) {
return this;
}
@Override
public void handoverFailed(String callId, ConnectionRequest request,
int error, Session.Info sessionInfo) {}
@Override
public void handoverComplete(String callId, Session.Info sessionInfo) {}
@Override
public void onCallFilteringCompleted(String callId,
Connection.CallFilteringCompletionInfo completionInfo, Session.Info sessionInfo) { }
}
FakeConnectionServiceDelegate mConnectionServiceDelegate;
private IConnectionService mConnectionServiceDelegateAdapter;
FakeConnectionService mConnectionService = new FakeConnectionService();
private IConnectionService.Stub mConnectionServiceSpy = Mockito.spy(mConnectionService);
public class ConnectionInfo {
PhoneAccountHandle connectionManagerPhoneAccount;
String id;
boolean ringing;
ConnectionRequest request;
boolean isIncoming;
boolean isUnknown;
int state;
int addressPresentation;
int capabilities;
int properties;
int supportedAudioRoutes;
StatusHints statusHints;
DisconnectCause disconnectCause;
String conferenceId;
String callerDisplayName;
int callerDisplayNamePresentation;
final List<String> conferenceableConnectionIds = new ArrayList<>();
IVideoProvider videoProvider;
Connection.VideoProvider videoProviderImpl;
MockVideoProvider mockVideoProvider;
int videoState;
boolean isVoipAudioMode;
Bundle extras;
boolean isConferenceCreated;
int callerNumberVerificationStatus;
}
public class ConferenceInfo {
PhoneAccountHandle phoneAccount;
int state;
int capabilities;
int properties;
final List<String> connectionIds = new ArrayList<>();
IVideoProvider videoProvider;
int videoState;
long connectTimeMillis;
long connectElapsedTimeMillis;
StatusHints statusHints;
Bundle extras;
}
public String mLatestConnectionId;
public Connection mLatestConnection;
public Conference mLatestConference;
public final Set<IConnectionServiceAdapter> mConnectionServiceAdapters = new HashSet<>();
public final Map<String, ConnectionInfo> mConnectionById = new HashMap<>();
public final Map<String, ConferenceInfo> mConferenceById = new HashMap<>();
public final List<ComponentName> mRemoteConnectionServiceNames = new ArrayList<>();
public final List<IBinder> mRemoteConnectionServices = new ArrayList<>();
public ConnectionServiceFixture(Context context) throws Exception {
mConnectionServiceDelegate = new FakeConnectionServiceDelegate(context);
mConnectionServiceDelegateAdapter = IConnectionService.Stub.asInterface(
mConnectionServiceDelegate.onBind(null));
}
@Override
public IConnectionService getTestDouble() {
return mConnectionServiceSpy;
}
public void sendHandleCreateConnectionComplete(String id) throws Exception {
for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
a.handleCreateConnectionComplete(
id,
mConnectionById.get(id).request,
parcelable(mConnectionById.get(id)), null /*Session.Info*/);
}
}
public void sendSetActive(String id) throws Exception {
mConnectionById.get(id).state = Connection.STATE_ACTIVE;
for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
a.setActive(id, null /*Session.Info*/);
}
}
public void sendSetRinging(String id) throws Exception {
mConnectionById.get(id).state = Connection.STATE_RINGING;
for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
a.setRinging(id, null /*Session.Info*/);
}
}
public void sendSetDialing(String id) throws Exception {
mConnectionById.get(id).state = Connection.STATE_DIALING;
for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
a.setDialing(id, null /*Session.Info*/);
}
}
public void sendSetDisconnected(String id, int disconnectCause) throws Exception {
mConnectionById.get(id).state = Connection.STATE_DISCONNECTED;
mConnectionById.get(id).disconnectCause = new DisconnectCause(disconnectCause);
for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
a.setDisconnected(id, mConnectionById.get(id).disconnectCause, null /*Session.Info*/);
}
}
public void sendSetOnHold(String id) throws Exception {
mConnectionById.get(id).state = Connection.STATE_HOLDING;
for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
a.setOnHold(id, null /*Session.Info*/);
}
}
public void sendSetRingbackRequested(String id) throws Exception {
for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
a.setRingbackRequested(id, mConnectionById.get(id).ringing, null /*Session.Info*/);
}
}
public void sendSetConnectionCapabilities(String id) throws Exception {
for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
a.setConnectionCapabilities(id, mConnectionById.get(id).capabilities,
null /*Session.Info*/);
}
}
public void sendSetConnectionProperties(String id) throws Exception {
for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
a.setConnectionProperties(id, mConnectionById.get(id).properties, null /*Session.Info*/);
}
}
public void sendSetIsConferenced(String id) throws Exception {
for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
a.setIsConferenced(id, mConnectionById.get(id).conferenceId, null /*Session.Info*/);
}
}
public void sendAddConferenceCall(String id) throws Exception {
for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
a.addConferenceCall(id, parcelable(mConferenceById.get(id)), null /*Session.Info*/);
}
}
public void sendRemoveCall(String id) throws Exception {
for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
a.removeCall(id, null /*Session.Info*/);
}
}
public void sendOnPostDialWait(String id, String remaining) throws Exception {
for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
a.onPostDialWait(id, remaining, null /*Session.Info*/);
}
}
public void sendOnPostDialChar(String id, char nextChar) throws Exception {
for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
a.onPostDialChar(id, nextChar, null /*Session.Info*/);
}
}
public void sendQueryRemoteConnectionServices() throws Exception {
mRemoteConnectionServices.clear();
for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
a.queryRemoteConnectionServices(new RemoteServiceCallback.Stub() {
@Override
public void onError() throws RemoteException {
throw new RuntimeException();
}
@Override
public void onResult(
List<ComponentName> names,
List<IBinder> services)
throws RemoteException {
TestCase.assertEquals(names.size(), services.size());
mRemoteConnectionServiceNames.addAll(names);
mRemoteConnectionServices.addAll(services);
}
@Override
public IBinder asBinder() {
return this;
}
}, "" /* callingPackage */, null /*Session.Info*/);
}
}
public void sendSetVideoProvider(String id) throws Exception {
for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
a.setVideoProvider(id, mConnectionById.get(id).videoProvider, null /*Session.Info*/);
}
}
public void sendSetVideoState(String id) throws Exception {
for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
a.setVideoState(id, mConnectionById.get(id).videoState, null /*Session.Info*/);
}
}
public void sendSetIsVoipAudioMode(String id) throws Exception {
for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
a.setIsVoipAudioMode(id, mConnectionById.get(id).isVoipAudioMode,
null /*Session.Info*/);
}
}
public void sendSetStatusHints(String id) throws Exception {
for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
a.setStatusHints(id, mConnectionById.get(id).statusHints, null /*Session.Info*/);
}
}
public void sendSetAddress(String id) throws Exception {
for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
a.setAddress(
id,
mConnectionById.get(id).request.getAddress(),
mConnectionById.get(id).addressPresentation, null /*Session.Info*/);
}
}
public void sendSetCallerDisplayName(String id) throws Exception {
for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
a.setCallerDisplayName(
id,
mConnectionById.get(id).callerDisplayName,
mConnectionById.get(id).callerDisplayNamePresentation, null /*Session.Info*/);
}
}
public void sendSetConferenceableConnections(String id) throws Exception {
for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
a.setConferenceableConnections(id, mConnectionById.get(id).conferenceableConnectionIds,
null /*Session.Info*/);
}
}
public void sendAddExistingConnection(String id) throws Exception {
for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
a.addExistingConnection(id, parcelable(mConnectionById.get(id)), null /*Session.Info*/);
}
}
public void sendConnectionEvent(String id, String event, Bundle extras) throws Exception {
for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
a.onConnectionEvent(id, event, extras, null /*Session.Info*/);
}
}
public void sendSetConferenceMergeFailed(String id) throws Exception {
for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
a.setConferenceMergeFailed(id, null /*Session.Info*/);
}
}
/**
* Waits until the {@link Connection#onExtrasChanged(Bundle)} API has been called on a
* {@link Connection} or {@link Conference}.
*/
public void waitForExtras() {
try {
mExtrasLock.await(TelecomSystemTest.TEST_TIMEOUT, TimeUnit.MILLISECONDS);
} catch (InterruptedException ie) {
}
mExtrasLock = new CountDownLatch(1);
}
public void waitForHandlerToClear() {
mConnectionServiceDelegate.getHandler().removeCallbacksAndMessages(null);
final CountDownLatch lock = new CountDownLatch(1);
mConnectionServiceDelegate.getHandler().post(lock::countDown);
while (lock.getCount() > 0) {
try {
lock.await(TelecomSystemTest.TEST_TIMEOUT, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
// do nothing
}
}
}
private ParcelableConference parcelable(ConferenceInfo c) {
return new ParcelableConference.Builder(c.phoneAccount, c.state)
.setConnectionCapabilities(c.capabilities)
.setConnectionProperties(c.properties)
.setConnectionIds(c.connectionIds)
.setVideoAttributes(c.videoProvider, c.videoState)
.setConnectTimeMillis(c.connectTimeMillis, c.connectElapsedTimeMillis)
.setStatusHints(c.statusHints)
.setExtras(c.extras)
.build();
}
private ParcelableConnection parcelable(ConnectionInfo c) {
return new ParcelableConnection(
c.request.getAccountHandle(),
c.state,
c.capabilities,
c.properties,
c.supportedAudioRoutes,
c.request.getAddress(),
c.addressPresentation,
c.callerDisplayName,
c.callerDisplayNamePresentation,
c.videoProvider,
c.videoState,
false, /* ringback requested */
false, /* voip audio mode */
0, /* Connect Time for conf call on this connection */
0, /* Connect Real Time comes from conference call */
c.statusHints,
c.disconnectCause,
c.conferenceableConnectionIds,
c.extras,
c.callerNumberVerificationStatus);
}
}