blob: 1da2e49adfdc490e5f62272a443802a28778b6bf [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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.isNotNull;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isNull;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.IContentProvider;
import android.content.Intent;
import android.media.AudioManager;
import android.media.IAudioService;
import android.media.ToneGenerator;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Process;
import android.os.UserHandle;
import android.provider.BlockedNumberContract;
import android.telecom.Call;
import android.telecom.ConnectionRequest;
import android.telecom.DisconnectCause;
import android.telecom.ParcelableCall;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import com.android.internal.telecom.IInCallAdapter;
import com.android.server.telecom.AsyncRingtonePlayer;
import com.android.server.telecom.BluetoothPhoneServiceImpl;
import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.CallAudioRouteStateMachine;
import com.android.server.telecom.CallerInfoLookupHelper;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.CallsManagerListenerBase;
import com.android.server.telecom.ClockProxy;
import com.android.server.telecom.ConnectionServiceFocusManager;
import com.android.server.telecom.DefaultDialerCache;
import com.android.server.telecom.HeadsetMediaButton;
import com.android.server.telecom.HeadsetMediaButtonFactory;
import com.android.server.telecom.InCallWakeLockController;
import com.android.server.telecom.InCallWakeLockControllerFactory;
import com.android.server.telecom.MissedCallNotifier;
import com.android.server.telecom.PhoneAccountRegistrar;
import com.android.server.telecom.PhoneNumberUtilsAdapter;
import com.android.server.telecom.PhoneNumberUtilsAdapterImpl;
import com.android.server.telecom.ProximitySensorManager;
import com.android.server.telecom.ProximitySensorManagerFactory;
import com.android.server.telecom.RoleManagerAdapter;
import com.android.server.telecom.StatusBarNotifier;
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.Timeouts;
import com.android.server.telecom.WiredHeadsetManager;
import com.android.server.telecom.bluetooth.BluetoothRouteManager;
import com.android.server.telecom.components.UserCallIntentProcessor;
import com.android.server.telecom.ui.IncomingCallNotifier;
import com.android.server.telecom.ui.MissedCallNotifierImpl.MissedCallNotifierImplFactory;
import com.google.common.base.Predicate;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Implements mocks and functionality required to implement telecom system tests.
*/
public class TelecomSystemTest extends TelecomTestCase {
static final int TEST_POLL_INTERVAL = 10; // milliseconds
static final int TEST_TIMEOUT = 1000; // milliseconds
// Purposely keep the connect time (which is wall clock) and elapsed time (which is time since
// boot) different to test that wall clock time operations and elapsed time operations perform
// as they individually should.
static final long TEST_CREATE_TIME = 100;
static final long TEST_CREATE_ELAPSED_TIME = 200;
static final long TEST_CONNECT_TIME = 1000;
static final long TEST_CONNECT_ELAPSED_TIME = 2000;
static final long TEST_DISCONNECT_TIME = 8000;
static final long TEST_DISCONNECT_ELAPSED_TIME = 4000;
public class HeadsetMediaButtonFactoryF implements HeadsetMediaButtonFactory {
@Override
public HeadsetMediaButton create(Context context, CallsManager callsManager,
TelecomSystem.SyncRoot lock) {
return mHeadsetMediaButton;
}
}
public class ProximitySensorManagerFactoryF implements ProximitySensorManagerFactory {
@Override
public ProximitySensorManager create(Context context, CallsManager callsManager) {
return mProximitySensorManager;
}
}
public class InCallWakeLockControllerFactoryF implements InCallWakeLockControllerFactory {
@Override
public InCallWakeLockController create(Context context, CallsManager callsManager) {
return mInCallWakeLockController;
}
}
public static class MissedCallNotifierFakeImpl extends CallsManagerListenerBase
implements MissedCallNotifier {
List<CallInfo> missedCallsNotified = new ArrayList<>();
@Override
public void clearMissedCalls(UserHandle userHandle) {
}
@Override
public void showMissedCallNotification(CallInfo call) {
missedCallsNotified.add(call);
}
@Override
public void reloadAfterBootComplete(CallerInfoLookupHelper callerInfoLookupHelper,
CallInfoFactory callInfoFactory) { }
@Override
public void reloadFromDatabase(CallerInfoLookupHelper callerInfoLookupHelper,
CallInfoFactory callInfoFactory, UserHandle userHandle) { }
@Override
public void setCurrentUserHandle(UserHandle userHandle) {
}
}
MissedCallNotifierFakeImpl mMissedCallNotifier = new MissedCallNotifierFakeImpl();
private class EmergencyNumberUtilsAdapter extends PhoneNumberUtilsAdapterImpl {
@Override
public boolean isLocalEmergencyNumber(Context context, String number) {
return mIsEmergencyCall;
}
@Override
public boolean isPotentialLocalEmergencyNumber(Context context, String number) {
return mIsEmergencyCall;
}
}
private class IncomingCallAddedListener extends CallsManagerListenerBase {
private final CountDownLatch mCountDownLatch;
public IncomingCallAddedListener(CountDownLatch latch) {
mCountDownLatch = latch;
}
@Override
public void onCallAdded(com.android.server.telecom.Call call) {
mCountDownLatch.countDown();
}
}
PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter = new EmergencyNumberUtilsAdapter();
@Mock HeadsetMediaButton mHeadsetMediaButton;
@Mock ProximitySensorManager mProximitySensorManager;
@Mock InCallWakeLockController mInCallWakeLockController;
@Mock BluetoothPhoneServiceImpl mBluetoothPhoneServiceImpl;
@Mock AsyncRingtonePlayer mAsyncRingtonePlayer;
@Mock IncomingCallNotifier mIncomingCallNotifier;
@Mock ClockProxy mClockProxy;
@Mock RoleManagerAdapter mRoleManagerAdapter;
final ComponentName mInCallServiceComponentNameX =
new ComponentName(
"incall-service-package-X",
"incall-service-class-X");
final ComponentName mInCallServiceComponentNameY =
new ComponentName(
"incall-service-package-Y",
"incall-service-class-Y");
InCallServiceFixture mInCallServiceFixtureX;
InCallServiceFixture mInCallServiceFixtureY;
final ComponentName mConnectionServiceComponentNameA =
new ComponentName(
"connection-service-package-A",
"connection-service-class-A");
final ComponentName mConnectionServiceComponentNameB =
new ComponentName(
"connection-service-package-B",
"connection-service-class-B");
final PhoneAccount mPhoneAccountA0 =
PhoneAccount.builder(
new PhoneAccountHandle(
mConnectionServiceComponentNameA,
"id A 0"),
"Phone account service A ID 0")
.addSupportedUriScheme("tel")
.setCapabilities(
PhoneAccount.CAPABILITY_CALL_PROVIDER |
PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION |
PhoneAccount.CAPABILITY_VIDEO_CALLING)
.build();
final PhoneAccount mPhoneAccountA1 =
PhoneAccount.builder(
new PhoneAccountHandle(
mConnectionServiceComponentNameA,
"id A 1"),
"Phone account service A ID 1")
.addSupportedUriScheme("tel")
.setCapabilities(
PhoneAccount.CAPABILITY_CALL_PROVIDER |
PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION |
PhoneAccount.CAPABILITY_VIDEO_CALLING)
.build();
final PhoneAccount mPhoneAccountA2 =
PhoneAccount.builder(
new PhoneAccountHandle(
mConnectionServiceComponentNameA,
"id A 2"),
"Phone account service A ID 2")
.addSupportedUriScheme("tel")
.setCapabilities(
PhoneAccount.CAPABILITY_CALL_PROVIDER |
PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
.build();
final PhoneAccount mPhoneAccountSelfManaged =
PhoneAccount.builder(
new PhoneAccountHandle(
mConnectionServiceComponentNameA,
"id SM"),
"Phone account service A SM")
.addSupportedUriScheme("tel")
.setCapabilities(
PhoneAccount.CAPABILITY_SELF_MANAGED)
.build();
final PhoneAccount mPhoneAccountB0 =
PhoneAccount.builder(
new PhoneAccountHandle(
mConnectionServiceComponentNameB,
"id B 0"),
"Phone account service B ID 0")
.addSupportedUriScheme("tel")
.setCapabilities(
PhoneAccount.CAPABILITY_CALL_PROVIDER |
PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION |
PhoneAccount.CAPABILITY_VIDEO_CALLING)
.build();
final PhoneAccount mPhoneAccountE0 =
PhoneAccount.builder(
new PhoneAccountHandle(
mConnectionServiceComponentNameA,
"id E 0"),
"Phone account service E ID 0")
.addSupportedUriScheme("tel")
.setCapabilities(
PhoneAccount.CAPABILITY_CALL_PROVIDER |
PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION |
PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)
.build();
final PhoneAccount mPhoneAccountE1 =
PhoneAccount.builder(
new PhoneAccountHandle(
mConnectionServiceComponentNameA,
"id E 1"),
"Phone account service E ID 1")
.addSupportedUriScheme("tel")
.setCapabilities(
PhoneAccount.CAPABILITY_CALL_PROVIDER |
PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION |
PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)
.build();
ConnectionServiceFixture mConnectionServiceFixtureA;
ConnectionServiceFixture mConnectionServiceFixtureB;
Timeouts.Adapter mTimeoutsAdapter;
CallerInfoAsyncQueryFactoryFixture mCallerInfoAsyncQueryFactoryFixture;
IAudioService mAudioService;
TelecomSystem mTelecomSystem;
Context mSpyContext;
private int mNumOutgoingCallsMade;
private boolean mIsEmergencyCall;
class IdPair {
final String mConnectionId;
final String mCallId;
public IdPair(String connectionId, String callId) {
this.mConnectionId = connectionId;
this.mCallId = callId;
}
}
@Override
public void setUp() throws Exception {
super.setUp();
mSpyContext = mComponentContextFixture.getTestDouble().getApplicationContext();
doReturn(mSpyContext).when(mSpyContext).getApplicationContext();
doNothing().when(mSpyContext).sendBroadcastAsUser(any(), any(), any());
mNumOutgoingCallsMade = 0;
mIsEmergencyCall = false;
// First set up information about the In-Call services in the mock Context, since
// Telecom will search for these as soon as it is instantiated
setupInCallServices();
// Next, create the TelecomSystem, our system under test
setupTelecomSystem();
// Finally, register the ConnectionServices with the PhoneAccountRegistrar of the
// now-running TelecomSystem
setupConnectionServices();
waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
}
@Override
public void tearDown() throws Exception {
mTelecomSystem.getCallsManager().getCallAudioManager()
.getCallAudioRouteStateMachine().quitNow();
mTelecomSystem.getCallsManager().getCallAudioManager()
.getCallAudioModeStateMachine().quitNow();
mTelecomSystem = null;
super.tearDown();
}
protected ParcelableCall makeConferenceCall() throws Exception {
IdPair callId1 = startAndMakeActiveOutgoingCall("650-555-1212",
mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
IdPair callId2 = startAndMakeActiveOutgoingCall("650-555-1213",
mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
IInCallAdapter inCallAdapter = mInCallServiceFixtureX.getInCallAdapter();
inCallAdapter.conference(callId1.mCallId, callId2.mCallId);
// Wait for the handler in ConnectionService
waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
ParcelableCall call1 = mInCallServiceFixtureX.getCall(callId1.mCallId);
ParcelableCall call2 = mInCallServiceFixtureX.getCall(callId2.mCallId);
// Check that the two calls end up with a parent in the end
assertNotNull(call1.getParentCallId());
assertNotNull(call2.getParentCallId());
assertEquals(call1.getParentCallId(), call2.getParentCallId());
// Check to make sure that the parent call made it to the in-call service
String parentCallId = call1.getParentCallId();
ParcelableCall conferenceCall = mInCallServiceFixtureX.getCall(parentCallId);
assertEquals(2, conferenceCall.getChildCallIds().size());
assertTrue(conferenceCall.getChildCallIds().contains(callId1.mCallId));
assertTrue(conferenceCall.getChildCallIds().contains(callId2.mCallId));
return conferenceCall;
}
private void setupTelecomSystem() throws Exception {
// Remove any cached PhoneAccount xml
File phoneAccountFile =
new File(mComponentContextFixture.getTestDouble()
.getApplicationContext().getFilesDir(),
PhoneAccountRegistrar.FILE_NAME);
if (phoneAccountFile.exists()) {
phoneAccountFile.delete();
}
// Use actual implementations instead of mocking the interface out.
HeadsetMediaButtonFactory headsetMediaButtonFactory =
spy(new HeadsetMediaButtonFactoryF());
ProximitySensorManagerFactory proximitySensorManagerFactory =
spy(new ProximitySensorManagerFactoryF());
InCallWakeLockControllerFactory inCallWakeLockControllerFactory =
spy(new InCallWakeLockControllerFactoryF());
mAudioService = setupAudioService();
mCallerInfoAsyncQueryFactoryFixture = new CallerInfoAsyncQueryFactoryFixture();
mTimeoutsAdapter = mock(Timeouts.Adapter.class);
when(mTimeoutsAdapter.getCallScreeningTimeoutMillis(any(ContentResolver.class)))
.thenReturn(TEST_TIMEOUT / 5L);
mIncomingCallNotifier = mock(IncomingCallNotifier.class);
mClockProxy = mock(ClockProxy.class);
when(mClockProxy.currentTimeMillis()).thenReturn(TEST_CREATE_TIME);
when(mClockProxy.elapsedRealtime()).thenReturn(TEST_CREATE_ELAPSED_TIME);
when(mRoleManagerAdapter.getCallCompanionApps()).thenReturn(Collections.emptyList());
when(mRoleManagerAdapter.getDefaultCallScreeningApp()).thenReturn(null);
when(mRoleManagerAdapter.getCarModeDialerApp()).thenReturn(null);
mTelecomSystem = new TelecomSystem(
mComponentContextFixture.getTestDouble(),
(context, phoneAccountRegistrar, defaultDialerCache) -> mMissedCallNotifier,
mCallerInfoAsyncQueryFactoryFixture.getTestDouble(),
headsetMediaButtonFactory,
proximitySensorManagerFactory,
inCallWakeLockControllerFactory,
() -> mAudioService,
(context, lock, callsManager, phoneAccountRegistrar) -> mBluetoothPhoneServiceImpl,
ConnectionServiceFocusManager::new,
mTimeoutsAdapter,
mAsyncRingtonePlayer,
mPhoneNumberUtilsAdapter,
mIncomingCallNotifier,
(streamType, volume) -> mock(ToneGenerator.class),
new CallAudioRouteStateMachine.Factory() {
@Override
public CallAudioRouteStateMachine create(
Context context,
CallsManager callsManager,
BluetoothRouteManager bluetoothManager,
WiredHeadsetManager wiredHeadsetManager,
StatusBarNotifier statusBarNotifier,
CallAudioManager.AudioServiceFactory audioServiceFactory,
int earpieceControl) {
return new CallAudioRouteStateMachine(context,
callsManager,
bluetoothManager,
wiredHeadsetManager,
statusBarNotifier,
audioServiceFactory,
// Force enable an earpiece for the end-to-end tests
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED);
}
},
mClockProxy,
mRoleManagerAdapter);
mComponentContextFixture.setTelecomManager(new TelecomManager(
mComponentContextFixture.getTestDouble(),
mTelecomSystem.getTelecomServiceImpl().getBinder()));
verify(headsetMediaButtonFactory).create(
eq(mComponentContextFixture.getTestDouble().getApplicationContext()),
any(CallsManager.class),
any(TelecomSystem.SyncRoot.class));
verify(proximitySensorManagerFactory).create(
eq(mComponentContextFixture.getTestDouble().getApplicationContext()),
any(CallsManager.class));
verify(inCallWakeLockControllerFactory).create(
eq(mComponentContextFixture.getTestDouble().getApplicationContext()),
any(CallsManager.class));
}
private void setupConnectionServices() throws Exception {
mConnectionServiceFixtureA = new ConnectionServiceFixture(mContext);
mConnectionServiceFixtureB = new ConnectionServiceFixture(mContext);
mComponentContextFixture.addConnectionService(mConnectionServiceComponentNameA,
mConnectionServiceFixtureA.getTestDouble());
mComponentContextFixture.addConnectionService(mConnectionServiceComponentNameB,
mConnectionServiceFixtureB.getTestDouble());
mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountA0);
mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountA1);
mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountA2);
mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountSelfManaged);
mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountB0);
mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountE0);
mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountE1);
mTelecomSystem.getPhoneAccountRegistrar().setUserSelectedOutgoingPhoneAccount(
mPhoneAccountA0.getAccountHandle(), Process.myUserHandle());
}
private void setupInCallServices() throws Exception {
mComponentContextFixture.putStringArrayResource(
com.android.internal.R.array.config_defaultRoleHolders,
new String[] { RoleManagerAdapter.ROLE_DIALER + ": "
+ mInCallServiceComponentNameX.getPackageName() });
mComponentContextFixture.putResource(
com.android.server.telecom.R.string.incall_default_class,
mInCallServiceComponentNameX.getClassName());
mComponentContextFixture.putBooleanResource(
com.android.internal.R.bool.config_voice_capable, true);
mInCallServiceFixtureX = new InCallServiceFixture();
mInCallServiceFixtureY = new InCallServiceFixture();
mComponentContextFixture.addInCallService(mInCallServiceComponentNameX,
mInCallServiceFixtureX.getTestDouble());
mComponentContextFixture.addInCallService(mInCallServiceComponentNameY,
mInCallServiceFixtureY.getTestDouble());
}
/**
* Helper method for setting up the fake audio service.
* Calls to the fake audio service need to toggle the return
* value of AudioManager#isMicrophoneMute.
* @return mock of IAudioService
*/
private IAudioService setupAudioService() {
IAudioService audioService = mock(IAudioService.class);
final AudioManager fakeAudioManager =
(AudioManager) mComponentContextFixture.getTestDouble()
.getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
try {
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock i) {
Object[] args = i.getArguments();
doReturn(args[0]).when(fakeAudioManager).isMicrophoneMute();
return null;
}
}).when(audioService)
.setMicrophoneMute(any(Boolean.class), any(String.class), any(Integer.class));
} catch (android.os.RemoteException e) {
// Do nothing, leave the faked microphone state as-is
}
return audioService;
}
protected String startOutgoingPhoneCallWithNoPhoneAccount(String number,
ConnectionServiceFixture connectionServiceFixture)
throws Exception {
startOutgoingPhoneCallWaitForBroadcaster(number, null,
connectionServiceFixture, Process.myUserHandle(), VideoProfile.STATE_AUDIO_ONLY,
false /*isEmergency*/);
return mInCallServiceFixtureX.mLatestCallId;
}
protected IdPair outgoingCallPhoneAccountSelected(PhoneAccountHandle phoneAccountHandle,
int startingNumConnections, int startingNumCalls,
ConnectionServiceFixture connectionServiceFixture) throws Exception {
IdPair ids = outgoingCallCreateConnectionComplete(startingNumConnections, startingNumCalls,
phoneAccountHandle, connectionServiceFixture);
connectionServiceFixture.sendSetDialing(ids.mConnectionId);
assertEquals(Call.STATE_DIALING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
assertEquals(Call.STATE_DIALING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
connectionServiceFixture.sendSetVideoState(ids.mConnectionId);
connectionServiceFixture.sendSetActive(ids.mConnectionId);
assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
return ids;
}
protected IdPair startOutgoingPhoneCall(String number, PhoneAccountHandle phoneAccountHandle,
ConnectionServiceFixture connectionServiceFixture, UserHandle initiatingUser)
throws Exception {
return startOutgoingPhoneCall(number, phoneAccountHandle, connectionServiceFixture,
initiatingUser, VideoProfile.STATE_AUDIO_ONLY);
}
protected IdPair startOutgoingPhoneCall(String number, PhoneAccountHandle phoneAccountHandle,
ConnectionServiceFixture connectionServiceFixture, UserHandle initiatingUser,
int videoState) throws Exception {
int startingNumConnections = connectionServiceFixture.mConnectionById.size();
int startingNumCalls = mInCallServiceFixtureX.mCallById.size();
startOutgoingPhoneCallPendingCreateConnection(number, phoneAccountHandle,
connectionServiceFixture, initiatingUser, videoState);
verify(connectionServiceFixture.getTestDouble(), timeout(TEST_TIMEOUT))
.createConnectionComplete(anyString(), any());
return outgoingCallCreateConnectionComplete(startingNumConnections, startingNumCalls,
phoneAccountHandle, connectionServiceFixture);
}
protected IdPair triggerEmergencyRedial(PhoneAccountHandle phoneAccountHandle,
ConnectionServiceFixture connectionServiceFixture, IdPair emergencyIds)
throws Exception {
int startingNumConnections = connectionServiceFixture.mConnectionById.size();
int startingNumCalls = mInCallServiceFixtureX.mCallById.size();
// Send the message to disconnect the Emergency call due to an error.
// CreateConnectionProcessor should now try the second SIM account
connectionServiceFixture.sendSetDisconnected(emergencyIds.mConnectionId,
DisconnectCause.ERROR);
waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
assertEquals(Call.STATE_DIALING, mInCallServiceFixtureX.getCall(
emergencyIds.mCallId).getState());
assertEquals(Call.STATE_DIALING, mInCallServiceFixtureY.getCall(
emergencyIds.mCallId).getState());
return redialingCallCreateConnectionComplete(startingNumConnections, startingNumCalls,
phoneAccountHandle, connectionServiceFixture);
}
protected IdPair startOutgoingEmergencyCall(String number,
PhoneAccountHandle phoneAccountHandle,
ConnectionServiceFixture connectionServiceFixture, UserHandle initiatingUser,
int videoState) throws Exception {
int startingNumConnections = connectionServiceFixture.mConnectionById.size();
int startingNumCalls = mInCallServiceFixtureX.mCallById.size();
mIsEmergencyCall = true;
// Call will not use the ordered broadcaster, since it is an Emergency Call
startOutgoingPhoneCallWaitForBroadcaster(number, phoneAccountHandle,
connectionServiceFixture, initiatingUser, videoState, true /*isEmergency*/);
return outgoingCallCreateConnectionComplete(startingNumConnections, startingNumCalls,
phoneAccountHandle, connectionServiceFixture);
}
protected void startOutgoingPhoneCallWaitForBroadcaster(String number,
PhoneAccountHandle phoneAccountHandle,
ConnectionServiceFixture connectionServiceFixture, UserHandle initiatingUser,
int videoState, boolean isEmergency) throws Exception {
reset(connectionServiceFixture.getTestDouble(), mInCallServiceFixtureX.getTestDouble(),
mInCallServiceFixtureY.getTestDouble());
assertEquals(mInCallServiceFixtureX.mCallById.size(),
mInCallServiceFixtureY.mCallById.size());
assertEquals((mInCallServiceFixtureX.mInCallAdapter != null),
(mInCallServiceFixtureY.mInCallAdapter != null));
mNumOutgoingCallsMade++;
boolean hasInCallAdapter = mInCallServiceFixtureX.mInCallAdapter != null;
Intent actionCallIntent = new Intent();
actionCallIntent.setData(Uri.parse("tel:" + number));
actionCallIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);
if(isEmergency) {
actionCallIntent.setAction(Intent.ACTION_CALL_EMERGENCY);
} else {
actionCallIntent.setAction(Intent.ACTION_CALL);
}
if (phoneAccountHandle != null) {
actionCallIntent.putExtra(
TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
phoneAccountHandle);
}
if (videoState != VideoProfile.STATE_AUDIO_ONLY) {
actionCallIntent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState);
}
final UserHandle userHandle = initiatingUser;
Context localAppContext = mComponentContextFixture.getTestDouble().getApplicationContext();
new UserCallIntentProcessor(localAppContext, userHandle).processIntent(
actionCallIntent, null, true /* hasCallAppOp*/, false /* isLocal */);
// Wait for handler to start CallerInfo lookup.
waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
// Send the CallerInfo lookup reply.
mCallerInfoAsyncQueryFactoryFixture.mRequests.forEach(
CallerInfoAsyncQueryFactoryFixture.Request::reply);
if (phoneAccountHandle != null) {
mTelecomSystem.getCallsManager().getLatestPostSelectionProcessingFuture().join();
}
waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
boolean isSelfManaged = phoneAccountHandle == mPhoneAccountSelfManaged.getAccountHandle();
if (!hasInCallAdapter && !isSelfManaged) {
verify(mInCallServiceFixtureX.getTestDouble(), timeout(TEST_TIMEOUT))
.setInCallAdapter(
any(IInCallAdapter.class));
verify(mInCallServiceFixtureY.getTestDouble(), timeout(TEST_TIMEOUT))
.setInCallAdapter(
any(IInCallAdapter.class));
}
}
protected String startOutgoingPhoneCallPendingCreateConnection(String number,
PhoneAccountHandle phoneAccountHandle,
ConnectionServiceFixture connectionServiceFixture, UserHandle initiatingUser,
int videoState) throws Exception {
startOutgoingPhoneCallWaitForBroadcaster(number,phoneAccountHandle,
connectionServiceFixture, initiatingUser, videoState, false /*isEmergency*/);
waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
verifyAndProcessOutgoingCallBroadcast(phoneAccountHandle);
return mInCallServiceFixtureX.mLatestCallId;
}
protected void verifyAndProcessOutgoingCallBroadcast(PhoneAccountHandle phoneAccountHandle) {
ArgumentCaptor<Intent> newOutgoingCallIntent =
ArgumentCaptor.forClass(Intent.class);
ArgumentCaptor<BroadcastReceiver> newOutgoingCallReceiver =
ArgumentCaptor.forClass(BroadcastReceiver.class);
if (phoneAccountHandle != mPhoneAccountSelfManaged.getAccountHandle()) {
verify(mComponentContextFixture.getTestDouble().getApplicationContext(),
times(mNumOutgoingCallsMade))
.sendOrderedBroadcastAsUser(
newOutgoingCallIntent.capture(),
any(UserHandle.class),
anyString(),
anyInt(),
newOutgoingCallReceiver.capture(),
nullable(Handler.class),
anyInt(),
anyString(),
nullable(Bundle.class));
// Pass on the new outgoing call Intent
// Set a dummy PendingResult so the BroadcastReceiver agrees to accept onReceive()
newOutgoingCallReceiver.getValue().setPendingResult(
new BroadcastReceiver.PendingResult(0, "", null, 0, true, false, null, 0, 0));
newOutgoingCallReceiver.getValue().setResultData(
newOutgoingCallIntent.getValue().getStringExtra(Intent.EXTRA_PHONE_NUMBER));
newOutgoingCallReceiver.getValue().onReceive(mComponentContextFixture.getTestDouble(),
newOutgoingCallIntent.getValue());
}
}
// When Telecom is redialing due to an error, we need to make sure the number of connections
// increase, but not the number of Calls in the InCallService.
protected IdPair redialingCallCreateConnectionComplete(int startingNumConnections,
int startingNumCalls, PhoneAccountHandle phoneAccountHandle,
ConnectionServiceFixture connectionServiceFixture) throws Exception {
assertEquals(startingNumConnections + 1, connectionServiceFixture.mConnectionById.size());
verify(connectionServiceFixture.getTestDouble())
.createConnection(eq(phoneAccountHandle), anyString(), any(ConnectionRequest.class),
eq(false)/*isIncoming*/, anyBoolean(), any());
// Wait for handleCreateConnectionComplete
waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
// Make sure the number of registered InCallService Calls stays the same.
assertEquals(startingNumCalls, mInCallServiceFixtureX.mCallById.size());
assertEquals(startingNumCalls, mInCallServiceFixtureY.mCallById.size());
assertEquals(mInCallServiceFixtureX.mLatestCallId, mInCallServiceFixtureY.mLatestCallId);
return new IdPair(connectionServiceFixture.mLatestConnectionId,
mInCallServiceFixtureX.mLatestCallId);
}
protected IdPair outgoingCallCreateConnectionComplete(int startingNumConnections,
int startingNumCalls, PhoneAccountHandle phoneAccountHandle,
ConnectionServiceFixture connectionServiceFixture) throws Exception {
// Wait for the focus tracker.
waitForHandlerAction(mTelecomSystem.getCallsManager()
.getConnectionServiceFocusManager().getHandler(), TEST_TIMEOUT);
verify(connectionServiceFixture.getTestDouble())
.createConnection(eq(phoneAccountHandle), anyString(), any(ConnectionRequest.class),
eq(false)/*isIncoming*/, anyBoolean(), any());
// Wait for handleCreateConnectionComplete
waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
assertEquals(startingNumConnections + 1,
connectionServiceFixture.mConnectionById.size());
// Wait for the callback in ConnectionService#onAdapterAttached to execute.
waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
// Ensure callback to CS on successful creation happened.
verify(connectionServiceFixture.getTestDouble(), timeout(TEST_TIMEOUT))
.createConnectionComplete(anyString(), any());
if (phoneAccountHandle == mPhoneAccountSelfManaged.getAccountHandle()) {
assertEquals(startingNumCalls, mInCallServiceFixtureX.mCallById.size());
assertEquals(startingNumCalls, mInCallServiceFixtureY.mCallById.size());
} else {
assertEquals(startingNumCalls + 1, mInCallServiceFixtureX.mCallById.size());
assertEquals(startingNumCalls + 1, mInCallServiceFixtureY.mCallById.size());
}
assertEquals(mInCallServiceFixtureX.mLatestCallId, mInCallServiceFixtureY.mLatestCallId);
return new IdPair(connectionServiceFixture.mLatestConnectionId,
mInCallServiceFixtureX.mLatestCallId);
}
protected IdPair startIncomingPhoneCall(
String number,
PhoneAccountHandle phoneAccountHandle,
final ConnectionServiceFixture connectionServiceFixture) throws Exception {
return startIncomingPhoneCall(number, phoneAccountHandle, VideoProfile.STATE_AUDIO_ONLY,
connectionServiceFixture);
}
protected IdPair startIncomingPhoneCall(
String number,
PhoneAccountHandle phoneAccountHandle,
int videoState,
final ConnectionServiceFixture connectionServiceFixture) throws Exception {
reset(connectionServiceFixture.getTestDouble(), mInCallServiceFixtureX.getTestDouble(),
mInCallServiceFixtureY.getTestDouble());
assertEquals(mInCallServiceFixtureX.mCallById.size(),
mInCallServiceFixtureY.mCallById.size());
assertEquals((mInCallServiceFixtureX.mInCallAdapter != null),
(mInCallServiceFixtureY.mInCallAdapter != null));
final int startingNumConnections = connectionServiceFixture.mConnectionById.size();
final int startingNumCalls = mInCallServiceFixtureX.mCallById.size();
boolean hasInCallAdapter = mInCallServiceFixtureX.mInCallAdapter != null;
connectionServiceFixture.mConnectionServiceDelegate.mVideoState = videoState;
CountDownLatch incomingCallAddedLatch = new CountDownLatch(1);
IncomingCallAddedListener callAddedListener =
new IncomingCallAddedListener(incomingCallAddedLatch);
mTelecomSystem.getCallsManager().addListener(callAddedListener);
Bundle extras = new Bundle();
extras.putParcelable(
TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null));
mTelecomSystem.getTelecomServiceImpl().getBinder()
.addNewIncomingCall(phoneAccountHandle, extras);
verify(connectionServiceFixture.getTestDouble())
.createConnection(any(PhoneAccountHandle.class), anyString(),
any(ConnectionRequest.class), eq(true), eq(false), any());
// Wait for the handler to start the CallerInfo lookup
waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
// Ensure callback to CS on successful creation happened.
verify(connectionServiceFixture.getTestDouble(), timeout(TEST_TIMEOUT))
.createConnectionComplete(anyString(), any());
// Process the CallerInfo lookup reply
mCallerInfoAsyncQueryFactoryFixture.mRequests.forEach(
CallerInfoAsyncQueryFactoryFixture.Request::reply);
//Wait for/Verify call blocking happened asynchronously
incomingCallAddedLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
// For the case of incoming calls, Telecom connecting the InCall services and adding the
// Call is triggered by the async completion of the CallerInfoAsyncQuery. Once the Call
// is added, future interactions as triggered by the ConnectionService, through the various
// test fixtures, will be synchronous.
if (phoneAccountHandle != mPhoneAccountSelfManaged.getAccountHandle()) {
if (!hasInCallAdapter) {
verify(mInCallServiceFixtureX.getTestDouble(), timeout(TEST_TIMEOUT))
.setInCallAdapter(any(IInCallAdapter.class));
verify(mInCallServiceFixtureY.getTestDouble(), timeout(TEST_TIMEOUT))
.setInCallAdapter(any(IInCallAdapter.class));
// Give the InCallService time to respond
assertTrueWithTimeout(new Predicate<Void>() {
@Override
public boolean apply(Void v) {
return mInCallServiceFixtureX.mInCallAdapter != null;
}
});
assertTrueWithTimeout(new Predicate<Void>() {
@Override
public boolean apply(Void v) {
return mInCallServiceFixtureY.mInCallAdapter != null;
}
});
verify(mInCallServiceFixtureX.getTestDouble(), timeout(TEST_TIMEOUT))
.addCall(any(ParcelableCall.class));
verify(mInCallServiceFixtureY.getTestDouble(), timeout(TEST_TIMEOUT))
.addCall(any(ParcelableCall.class));
// Give the InCallService time to respond
}
assertTrueWithTimeout(new Predicate<Void>() {
@Override
public boolean apply(Void v) {
return startingNumConnections + 1 ==
connectionServiceFixture.mConnectionById.size();
}
});
mInCallServiceFixtureX.waitUntilNumCalls(startingNumCalls + 1);
mInCallServiceFixtureY.waitUntilNumCalls(startingNumCalls + 1);
assertEquals(startingNumCalls + 1, mInCallServiceFixtureX.mCallById.size());
assertEquals(startingNumCalls + 1, mInCallServiceFixtureY.mCallById.size());
assertEquals(mInCallServiceFixtureX.mLatestCallId,
mInCallServiceFixtureY.mLatestCallId);
}
return new IdPair(connectionServiceFixture.mLatestConnectionId,
mInCallServiceFixtureX.mLatestCallId);
}
protected IdPair startAndMakeActiveOutgoingCall(
String number,
PhoneAccountHandle phoneAccountHandle,
ConnectionServiceFixture connectionServiceFixture) throws Exception {
return startAndMakeActiveOutgoingCall(number, phoneAccountHandle, connectionServiceFixture,
VideoProfile.STATE_AUDIO_ONLY);
}
// A simple outgoing call, verifying that the appropriate connection service is contacted,
// the proper lifecycle is followed, and both In-Call Services are updated correctly.
protected IdPair startAndMakeActiveOutgoingCall(
String number,
PhoneAccountHandle phoneAccountHandle,
ConnectionServiceFixture connectionServiceFixture, int videoState) throws Exception {
IdPair ids = startOutgoingPhoneCall(number, phoneAccountHandle, connectionServiceFixture,
Process.myUserHandle(), videoState);
connectionServiceFixture.sendSetDialing(ids.mConnectionId);
if (phoneAccountHandle != mPhoneAccountSelfManaged.getAccountHandle()) {
assertEquals(Call.STATE_DIALING,
mInCallServiceFixtureX.getCall(ids.mCallId).getState());
assertEquals(Call.STATE_DIALING,
mInCallServiceFixtureY.getCall(ids.mCallId).getState());
}
connectionServiceFixture.sendSetVideoState(ids.mConnectionId);
when(mClockProxy.currentTimeMillis()).thenReturn(TEST_CONNECT_TIME);
when(mClockProxy.elapsedRealtime()).thenReturn(TEST_CONNECT_ELAPSED_TIME);
connectionServiceFixture.sendSetActive(ids.mConnectionId);
if (phoneAccountHandle != mPhoneAccountSelfManaged.getAccountHandle()) {
assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
}
return ids;
}
protected IdPair startAndMakeActiveIncomingCall(
String number,
PhoneAccountHandle phoneAccountHandle,
ConnectionServiceFixture connectionServiceFixture) throws Exception {
return startAndMakeActiveIncomingCall(number, phoneAccountHandle, connectionServiceFixture,
VideoProfile.STATE_AUDIO_ONLY);
}
// A simple incoming call, similar in scope to the previous test
protected IdPair startAndMakeActiveIncomingCall(
String number,
PhoneAccountHandle phoneAccountHandle,
ConnectionServiceFixture connectionServiceFixture,
int videoState) throws Exception {
IdPair ids = startIncomingPhoneCall(number, phoneAccountHandle, connectionServiceFixture);
if (phoneAccountHandle != mPhoneAccountSelfManaged.getAccountHandle()) {
assertEquals(Call.STATE_RINGING,
mInCallServiceFixtureX.getCall(ids.mCallId).getState());
assertEquals(Call.STATE_RINGING,
mInCallServiceFixtureY.getCall(ids.mCallId).getState());
mInCallServiceFixtureX.mInCallAdapter
.answerCall(ids.mCallId, videoState);
// Wait on the CS focus manager handler
waitForHandlerAction(mTelecomSystem.getCallsManager()
.getConnectionServiceFocusManager().getHandler(), TEST_TIMEOUT);
if (!VideoProfile.isVideo(videoState)) {
verify(connectionServiceFixture.getTestDouble(), timeout(TEST_TIMEOUT))
.answer(eq(ids.mConnectionId), any());
} else {
verify(connectionServiceFixture.getTestDouble(), timeout(TEST_TIMEOUT))
.answerVideo(eq(ids.mConnectionId), eq(videoState), any());
}
}
when(mClockProxy.currentTimeMillis()).thenReturn(TEST_CONNECT_TIME);
when(mClockProxy.elapsedRealtime()).thenReturn(TEST_CONNECT_ELAPSED_TIME);
connectionServiceFixture.sendSetActive(ids.mConnectionId);
if (phoneAccountHandle != mPhoneAccountSelfManaged.getAccountHandle()) {
assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
}
return ids;
}
protected IdPair startAndMakeDialingEmergencyCall(
String number,
PhoneAccountHandle phoneAccountHandle,
ConnectionServiceFixture connectionServiceFixture) throws Exception {
IdPair ids = startOutgoingEmergencyCall(number, phoneAccountHandle,
connectionServiceFixture, Process.myUserHandle(), VideoProfile.STATE_AUDIO_ONLY);
connectionServiceFixture.sendSetDialing(ids.mConnectionId);
assertEquals(Call.STATE_DIALING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
assertEquals(Call.STATE_DIALING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
return ids;
}
protected static void assertTrueWithTimeout(Predicate<Void> predicate) {
int elapsed = 0;
while (elapsed < TEST_TIMEOUT) {
if (predicate.apply(null)) {
return;
} else {
try {
Thread.sleep(TEST_POLL_INTERVAL);
elapsed += TEST_POLL_INTERVAL;
} catch (InterruptedException e) {
fail(e.toString());
}
}
}
fail("Timeout in assertTrueWithTimeout");
}
}