/* * 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.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.app.NotificationManager; 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.CallerInfoLookupHelper; import com.android.server.telecom.CallsManager; import com.android.server.telecom.CallsManagerListenerBase; import com.android.server.telecom.ClockProxy; 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.TelecomSystem; import com.android.server.telecom.Timeouts; 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.util.ArrayList; 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 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; 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 wacky non-deterministic behavior Thread.sleep(200); 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 { // 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); mTelecomSystem = new TelecomSystem( mComponentContextFixture.getTestDouble(), new MissedCallNotifierImplFactory() { @Override public MissedCallNotifier makeMissedCallNotifierImpl(Context context, PhoneAccountRegistrar phoneAccountRegistrar, DefaultDialerCache defaultDialerCache) { return mMissedCallNotifier; } }, mCallerInfoAsyncQueryFactoryFixture.getTestDouble(), headsetMediaButtonFactory, proximitySensorManagerFactory, inCallWakeLockControllerFactory, new CallAudioManager.AudioServiceFactory() { @Override public IAudioService getAudioService() { return mAudioService; } }, new BluetoothPhoneServiceImpl.BluetoothPhoneServiceImplFactory() { @Override public BluetoothPhoneServiceImpl makeBluetoothPhoneServiceImpl(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager, PhoneAccountRegistrar phoneAccountRegistrar) { return mBluetoothPhoneServiceImpl; } }, mTimeoutsAdapter, mAsyncRingtonePlayer, mPhoneNumberUtilsAdapter, mIncomingCallNotifier, (streamType, volume) -> mock(ToneGenerator.class), mClockProxy); 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(); mConnectionServiceFixtureB = new ConnectionServiceFixture(); 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.putResource( com.android.server.telecom.R.string.ui_default_package, 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 { return startOutgoingPhoneCallPendingCreateConnection(number, null, connectionServiceFixture, Process.myUserHandle(), VideoProfile.STATE_AUDIO_ONLY); } 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); 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*/); // UserCallIntentProcessor's mContext.sendBroadcastAsUser(...) will call to an empty method // as to not actually try to send an intent to PrimaryCallReceiver. We verify that it was // called correctly in order to continue. verify(localAppContext).sendBroadcastAsUser(actionCallIntent, UserHandle.SYSTEM); mTelecomSystem.getCallIntentProcessor().processIntent(actionCallIntent); // 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); boolean isSelfManaged = phoneAccountHandle == mPhoneAccountSelfManaged.getAccountHandle(); if (!hasInCallAdapter && !isSelfManaged) { verify(mInCallServiceFixtureX.getTestDouble()) .setInCallAdapter( any(IInCallAdapter.class)); verify(mInCallServiceFixtureY.getTestDouble()) .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*/); ArgumentCaptor newOutgoingCallIntent = ArgumentCaptor.forClass(Intent.class); ArgumentCaptor 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()); } return mInCallServiceFixtureX.mLatestCallId; } // 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 { 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); // 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); IContentProvider blockedNumberProvider = mSpyContext.getContentResolver().acquireProvider(BlockedNumberContract.AUTHORITY); verify(blockedNumberProvider, timeout(TEST_TIMEOUT)).call( anyString(), eq(BlockedNumberContract.SystemContract.METHOD_SHOULD_SYSTEM_BLOCK_NUMBER), eq(number), isNull(Bundle.class)); // 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 (!hasInCallAdapter && phoneAccountHandle != mPhoneAccountSelfManaged.getAccountHandle()) { 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() { @Override public boolean apply(Void v) { return mInCallServiceFixtureX.mInCallAdapter != null; } }); assertTrueWithTimeout(new Predicate() { @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() { @Override public boolean apply(Void v) { return startingNumConnections + 1 == connectionServiceFixture.mConnectionById.size(); } }); assertTrueWithTimeout(new Predicate() { @Override public boolean apply(Void v) { return startingNumCalls + 1 == mInCallServiceFixtureX.mCallById.size(); } }); assertTrueWithTimeout(new Predicate() { @Override public boolean apply(Void v) { return 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); if (!VideoProfile.isVideo(videoState)) { verify(connectionServiceFixture.getTestDouble()) .answer(eq(ids.mConnectionId), any()); } else { verify(connectionServiceFixture.getTestDouble()) .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 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"); } }