TelecomSystemTest.java revision 369fd7887da98a4eb55944c92adc9abf481b845f
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.telecom.tests;
18
19
20import static org.mockito.ArgumentMatchers.nullable;
21import static org.mockito.Matchers.any;
22import static org.mockito.Matchers.anyBoolean;
23import static org.mockito.Matchers.anyInt;
24import static org.mockito.Matchers.anyString;
25import static org.mockito.Matchers.eq;
26import static org.mockito.Matchers.isNull;
27import static org.mockito.Mockito.doAnswer;
28import static org.mockito.Mockito.doNothing;
29import static org.mockito.Mockito.doReturn;
30import static org.mockito.Mockito.mock;
31import static org.mockito.Mockito.reset;
32import static org.mockito.Mockito.spy;
33import static org.mockito.Mockito.timeout;
34import static org.mockito.Mockito.times;
35import static org.mockito.Mockito.verify;
36import static org.mockito.Mockito.when;
37
38import android.app.NotificationManager;
39import android.content.BroadcastReceiver;
40import android.content.ComponentName;
41import android.content.ContentResolver;
42import android.content.Context;
43import android.content.IContentProvider;
44import android.content.Intent;
45import android.media.AudioManager;
46import android.media.IAudioService;
47import android.net.Uri;
48import android.os.Bundle;
49import android.os.Handler;
50import android.os.Looper;
51import android.os.Process;
52import android.os.UserHandle;
53import android.provider.BlockedNumberContract;
54import android.telecom.Call;
55import android.telecom.ConnectionRequest;
56import android.telecom.DisconnectCause;
57import android.telecom.ParcelableCall;
58import android.telecom.PhoneAccount;
59import android.telecom.PhoneAccountHandle;
60import android.telecom.TelecomManager;
61import android.telecom.VideoProfile;
62
63import com.android.internal.telecom.IInCallAdapter;
64import com.android.server.telecom.AsyncRingtonePlayer;
65import com.android.server.telecom.BluetoothPhoneServiceImpl;
66import com.android.server.telecom.CallAudioManager;
67import com.android.server.telecom.CallAudioRouteStateMachine;
68import com.android.server.telecom.CallerInfoAsyncQueryFactory;
69import com.android.server.telecom.CallerInfoLookupHelper;
70import com.android.server.telecom.CallsManager;
71import com.android.server.telecom.CallsManagerListenerBase;
72import com.android.server.telecom.DefaultDialerCache;
73import com.android.server.telecom.HeadsetMediaButton;
74import com.android.server.telecom.HeadsetMediaButtonFactory;
75import com.android.server.telecom.InCallWakeLockController;
76import com.android.server.telecom.InCallWakeLockControllerFactory;
77import com.android.server.telecom.InterruptionFilterProxy;
78import com.android.server.telecom.MissedCallNotifier;
79import com.android.server.telecom.PhoneAccountRegistrar;
80import com.android.server.telecom.PhoneNumberUtilsAdapter;
81import com.android.server.telecom.PhoneNumberUtilsAdapterImpl;
82import com.android.server.telecom.ProximitySensorManager;
83import com.android.server.telecom.ProximitySensorManagerFactory;
84import com.android.server.telecom.TelecomSystem;
85import com.android.server.telecom.Timeouts;
86import com.android.server.telecom.callfiltering.AsyncBlockCheckFilter;
87import com.android.server.telecom.components.UserCallIntentProcessor;
88import com.android.server.telecom.ui.IncomingCallNotifier;
89import com.android.server.telecom.ui.MissedCallNotifierImpl.MissedCallNotifierImplFactory;
90
91import com.google.common.base.Predicate;
92
93import org.mockito.ArgumentCaptor;
94import org.mockito.Mock;
95import org.mockito.invocation.InvocationOnMock;
96import org.mockito.stubbing.Answer;
97
98import java.util.ArrayList;
99import java.util.List;
100import java.util.concurrent.CountDownLatch;
101import java.util.concurrent.TimeUnit;
102
103/**
104 * Implements mocks and functionality required to implement telecom system tests.
105 */
106public class TelecomSystemTest extends TelecomTestCase {
107
108    static final int TEST_POLL_INTERVAL = 10;  // milliseconds
109    static final int TEST_TIMEOUT = 1000;  // milliseconds
110
111    public class HeadsetMediaButtonFactoryF implements HeadsetMediaButtonFactory  {
112        @Override
113        public HeadsetMediaButton create(Context context, CallsManager callsManager,
114                TelecomSystem.SyncRoot lock) {
115            return mHeadsetMediaButton;
116        }
117    }
118
119    public class ProximitySensorManagerFactoryF implements ProximitySensorManagerFactory {
120        @Override
121        public ProximitySensorManager create(Context context, CallsManager callsManager) {
122            return mProximitySensorManager;
123        }
124    }
125
126    public class InCallWakeLockControllerFactoryF implements InCallWakeLockControllerFactory {
127        @Override
128        public InCallWakeLockController create(Context context, CallsManager callsManager) {
129            return mInCallWakeLockController;
130        }
131    }
132
133    public static class MissedCallNotifierFakeImpl extends CallsManagerListenerBase
134            implements MissedCallNotifier {
135        List<CallInfo> missedCallsNotified = new ArrayList<>();
136
137        @Override
138        public void clearMissedCalls(UserHandle userHandle) {
139
140        }
141
142        @Override
143        public void showMissedCallNotification(CallInfo call) {
144            missedCallsNotified.add(call);
145        }
146
147        @Override
148        public void reloadAfterBootComplete(CallerInfoLookupHelper callerInfoLookupHelper,
149                CallInfoFactory callInfoFactory) { }
150
151        @Override
152        public void reloadFromDatabase(CallerInfoLookupHelper callerInfoLookupHelper,
153                CallInfoFactory callInfoFactory, UserHandle userHandle) { }
154
155        @Override
156        public void setCurrentUserHandle(UserHandle userHandle) {
157
158        }
159    }
160
161    MissedCallNotifierFakeImpl mMissedCallNotifier = new MissedCallNotifierFakeImpl();
162    private class EmergencyNumberUtilsAdapter extends PhoneNumberUtilsAdapterImpl {
163
164        @Override
165        public boolean isLocalEmergencyNumber(Context context, String number) {
166            return mIsEmergencyCall;
167        }
168
169        @Override
170        public boolean isPotentialLocalEmergencyNumber(Context context, String number) {
171            return mIsEmergencyCall;
172        }
173    }
174    PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter = new EmergencyNumberUtilsAdapter();
175
176    public static class MockInterruptionFilterProxy implements InterruptionFilterProxy {
177        private int mInterruptionFilter = NotificationManager.INTERRUPTION_FILTER_ALL;
178        @Override
179        public void setInterruptionFilter(int interruptionFilter) {
180            mInterruptionFilter = interruptionFilter;
181        }
182
183        @Override
184        public int getCurrentInterruptionFilter() {
185            return mInterruptionFilter;
186        }
187
188        @Override
189        public String getInterruptionModeInitiator() {
190            return "com.android.server.telecom";
191        }
192    }
193    @Mock HeadsetMediaButton mHeadsetMediaButton;
194    @Mock ProximitySensorManager mProximitySensorManager;
195    @Mock InCallWakeLockController mInCallWakeLockController;
196    @Mock BluetoothPhoneServiceImpl mBluetoothPhoneServiceImpl;
197    @Mock AsyncRingtonePlayer mAsyncRingtonePlayer;
198    @Mock InterruptionFilterProxy mInterruptionFilterProxy;
199    @Mock IncomingCallNotifier mIncomingCallNotifier;
200
201    final ComponentName mInCallServiceComponentNameX =
202            new ComponentName(
203                    "incall-service-package-X",
204                    "incall-service-class-X");
205    final ComponentName mInCallServiceComponentNameY =
206            new ComponentName(
207                    "incall-service-package-Y",
208                    "incall-service-class-Y");
209
210    InCallServiceFixture mInCallServiceFixtureX;
211    InCallServiceFixture mInCallServiceFixtureY;
212
213    final ComponentName mConnectionServiceComponentNameA =
214            new ComponentName(
215                    "connection-service-package-A",
216                    "connection-service-class-A");
217    final ComponentName mConnectionServiceComponentNameB =
218            new ComponentName(
219                    "connection-service-package-B",
220                    "connection-service-class-B");
221
222    final PhoneAccount mPhoneAccountA0 =
223            PhoneAccount.builder(
224                    new PhoneAccountHandle(
225                            mConnectionServiceComponentNameA,
226                            "id A 0"),
227                    "Phone account service A ID 0")
228                    .addSupportedUriScheme("tel")
229                    .setCapabilities(
230                            PhoneAccount.CAPABILITY_CALL_PROVIDER |
231                                    PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION |
232                                    PhoneAccount.CAPABILITY_VIDEO_CALLING)
233                    .build();
234    final PhoneAccount mPhoneAccountA1 =
235            PhoneAccount.builder(
236                    new PhoneAccountHandle(
237                            mConnectionServiceComponentNameA,
238                            "id A 1"),
239                    "Phone account service A ID 1")
240                    .addSupportedUriScheme("tel")
241                    .setCapabilities(
242                            PhoneAccount.CAPABILITY_CALL_PROVIDER |
243                                    PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION |
244                                    PhoneAccount.CAPABILITY_VIDEO_CALLING)
245                    .build();
246    final PhoneAccount mPhoneAccountA2 =
247            PhoneAccount.builder(
248                    new PhoneAccountHandle(
249                            mConnectionServiceComponentNameA,
250                            "id A 2"),
251                    "Phone account service A ID 2")
252                    .addSupportedUriScheme("tel")
253                    .setCapabilities(
254                            PhoneAccount.CAPABILITY_CALL_PROVIDER |
255                                    PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
256                    .build();
257    final PhoneAccount mPhoneAccountB0 =
258            PhoneAccount.builder(
259                    new PhoneAccountHandle(
260                            mConnectionServiceComponentNameB,
261                            "id B 0"),
262                    "Phone account service B ID 0")
263                    .addSupportedUriScheme("tel")
264                    .setCapabilities(
265                            PhoneAccount.CAPABILITY_CALL_PROVIDER |
266                                    PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION |
267                                    PhoneAccount.CAPABILITY_VIDEO_CALLING)
268                    .build();
269    final PhoneAccount mPhoneAccountE0 =
270            PhoneAccount.builder(
271                    new PhoneAccountHandle(
272                            mConnectionServiceComponentNameA,
273                            "id E 0"),
274                    "Phone account service E ID 0")
275                    .addSupportedUriScheme("tel")
276                    .setCapabilities(
277                            PhoneAccount.CAPABILITY_CALL_PROVIDER |
278                                    PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION |
279                                    PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)
280                    .build();
281
282    final PhoneAccount mPhoneAccountE1 =
283            PhoneAccount.builder(
284                    new PhoneAccountHandle(
285                            mConnectionServiceComponentNameA,
286                            "id E 1"),
287                    "Phone account service E ID 1")
288                    .addSupportedUriScheme("tel")
289                    .setCapabilities(
290                            PhoneAccount.CAPABILITY_CALL_PROVIDER |
291                                    PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION |
292                                    PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)
293                    .build();
294
295    ConnectionServiceFixture mConnectionServiceFixtureA;
296    ConnectionServiceFixture mConnectionServiceFixtureB;
297    Timeouts.Adapter mTimeoutsAdapter;
298
299    CallerInfoAsyncQueryFactoryFixture mCallerInfoAsyncQueryFactoryFixture;
300
301    IAudioService mAudioService;
302
303    TelecomSystem mTelecomSystem;
304
305    Context mSpyContext;
306
307    private int mNumOutgoingCallsMade;
308
309    private boolean mIsEmergencyCall;
310
311    class IdPair {
312        final String mConnectionId;
313        final String mCallId;
314
315        public IdPair(String connectionId, String callId) {
316            this.mConnectionId = connectionId;
317            this.mCallId = callId;
318        }
319    }
320
321    @Override
322    public void setUp() throws Exception {
323        super.setUp();
324        mSpyContext = mComponentContextFixture.getTestDouble().getApplicationContext();
325        doReturn(mSpyContext).when(mSpyContext).getApplicationContext();
326        doNothing().when(mSpyContext).sendBroadcastAsUser(any(), any(), any());
327
328        mNumOutgoingCallsMade = 0;
329
330        mIsEmergencyCall = false;
331
332        // First set up information about the In-Call services in the mock Context, since
333        // Telecom will search for these as soon as it is instantiated
334        setupInCallServices();
335
336        // Next, create the TelecomSystem, our system under test
337        setupTelecomSystem();
338
339        // Finally, register the ConnectionServices with the PhoneAccountRegistrar of the
340        // now-running TelecomSystem
341        setupConnectionServices();
342    }
343
344    @Override
345    public void tearDown() throws Exception {
346        mTelecomSystem.getCallsManager().getCallAudioManager()
347                .getCallAudioRouteStateMachine().quitNow();
348        mTelecomSystem.getCallsManager().getCallAudioManager()
349                .getCallAudioModeStateMachine().quitNow();
350        mTelecomSystem = null;
351        super.tearDown();
352    }
353
354    protected ParcelableCall makeConferenceCall() throws Exception {
355        IdPair callId1 = startAndMakeActiveOutgoingCall("650-555-1212",
356                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
357
358        IdPair callId2 = startAndMakeActiveOutgoingCall("650-555-1213",
359                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
360
361        IInCallAdapter inCallAdapter = mInCallServiceFixtureX.getInCallAdapter();
362        inCallAdapter.conference(callId1.mCallId, callId2.mCallId);
363        // Wait for wacky non-deterministic behavior
364        Thread.sleep(200);
365        ParcelableCall call1 = mInCallServiceFixtureX.getCall(callId1.mCallId);
366        ParcelableCall call2 = mInCallServiceFixtureX.getCall(callId2.mCallId);
367        // Check that the two calls end up with a parent in the end
368        assertNotNull(call1.getParentCallId());
369        assertNotNull(call2.getParentCallId());
370        assertEquals(call1.getParentCallId(), call2.getParentCallId());
371
372        // Check to make sure that the parent call made it to the in-call service
373        String parentCallId = call1.getParentCallId();
374        ParcelableCall conferenceCall = mInCallServiceFixtureX.getCall(parentCallId);
375        assertEquals(2, conferenceCall.getChildCallIds().size());
376        assertTrue(conferenceCall.getChildCallIds().contains(callId1.mCallId));
377        assertTrue(conferenceCall.getChildCallIds().contains(callId2.mCallId));
378        return conferenceCall;
379    }
380
381    private void setupTelecomSystem() throws Exception {
382        // Use actual implementations instead of mocking the interface out.
383        HeadsetMediaButtonFactory headsetMediaButtonFactory =
384                spy(new HeadsetMediaButtonFactoryF());
385        ProximitySensorManagerFactory proximitySensorManagerFactory =
386                spy(new ProximitySensorManagerFactoryF());
387        InCallWakeLockControllerFactory inCallWakeLockControllerFactory =
388                spy(new InCallWakeLockControllerFactoryF());
389        mAudioService = setupAudioService();
390
391        mCallerInfoAsyncQueryFactoryFixture = new CallerInfoAsyncQueryFactoryFixture();
392
393        mTimeoutsAdapter = mock(Timeouts.Adapter.class);
394        when(mTimeoutsAdapter.getCallScreeningTimeoutMillis(any(ContentResolver.class)))
395                .thenReturn(TEST_TIMEOUT / 5L);
396        mIncomingCallNotifier = mock(IncomingCallNotifier.class);
397
398        mTelecomSystem = new TelecomSystem(
399                mComponentContextFixture.getTestDouble(),
400                new MissedCallNotifierImplFactory() {
401                    @Override
402                    public MissedCallNotifier makeMissedCallNotifierImpl(Context context,
403                            PhoneAccountRegistrar phoneAccountRegistrar,
404                            DefaultDialerCache defaultDialerCache) {
405                        return mMissedCallNotifier;
406                    }
407                },
408                mCallerInfoAsyncQueryFactoryFixture.getTestDouble(),
409                headsetMediaButtonFactory,
410                proximitySensorManagerFactory,
411                inCallWakeLockControllerFactory,
412                new CallAudioManager.AudioServiceFactory() {
413                    @Override
414                    public IAudioService getAudioService() {
415                        return mAudioService;
416                    }
417                },
418                new BluetoothPhoneServiceImpl.BluetoothPhoneServiceImplFactory() {
419                    @Override
420                    public BluetoothPhoneServiceImpl makeBluetoothPhoneServiceImpl(Context context,
421                            TelecomSystem.SyncRoot lock, CallsManager callsManager,
422                            PhoneAccountRegistrar phoneAccountRegistrar) {
423                        return mBluetoothPhoneServiceImpl;
424                    }
425                },
426                mTimeoutsAdapter,
427                mAsyncRingtonePlayer,
428                mPhoneNumberUtilsAdapter,
429                mInterruptionFilterProxy,
430                mIncomingCallNotifier);
431
432        mComponentContextFixture.setTelecomManager(new TelecomManager(
433                mComponentContextFixture.getTestDouble(),
434                mTelecomSystem.getTelecomServiceImpl().getBinder()));
435
436        verify(headsetMediaButtonFactory).create(
437                eq(mComponentContextFixture.getTestDouble().getApplicationContext()),
438                any(CallsManager.class),
439                any(TelecomSystem.SyncRoot.class));
440        verify(proximitySensorManagerFactory).create(
441                eq(mComponentContextFixture.getTestDouble().getApplicationContext()),
442                any(CallsManager.class));
443        verify(inCallWakeLockControllerFactory).create(
444                eq(mComponentContextFixture.getTestDouble().getApplicationContext()),
445                any(CallsManager.class));
446    }
447
448    private void setupConnectionServices() throws Exception {
449        mConnectionServiceFixtureA = new ConnectionServiceFixture();
450        mConnectionServiceFixtureB = new ConnectionServiceFixture();
451
452        mComponentContextFixture.addConnectionService(mConnectionServiceComponentNameA,
453                mConnectionServiceFixtureA.getTestDouble());
454        mComponentContextFixture.addConnectionService(mConnectionServiceComponentNameB,
455                mConnectionServiceFixtureB.getTestDouble());
456
457        mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountA0);
458        mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountA1);
459        mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountA2);
460        mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountB0);
461        mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountE0);
462        mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountE1);
463
464        mTelecomSystem.getPhoneAccountRegistrar().setUserSelectedOutgoingPhoneAccount(
465                mPhoneAccountA0.getAccountHandle(), Process.myUserHandle());
466    }
467
468    private void setupInCallServices() throws Exception {
469        mComponentContextFixture.putResource(
470                com.android.server.telecom.R.string.ui_default_package,
471                mInCallServiceComponentNameX.getPackageName());
472        mComponentContextFixture.putResource(
473                com.android.server.telecom.R.string.incall_default_class,
474                mInCallServiceComponentNameX.getClassName());
475        mComponentContextFixture.putBooleanResource(
476                com.android.internal.R.bool.config_voice_capable, true);
477
478        mInCallServiceFixtureX = new InCallServiceFixture();
479        mInCallServiceFixtureY = new InCallServiceFixture();
480
481        mComponentContextFixture.addInCallService(mInCallServiceComponentNameX,
482                mInCallServiceFixtureX.getTestDouble());
483        mComponentContextFixture.addInCallService(mInCallServiceComponentNameY,
484                mInCallServiceFixtureY.getTestDouble());
485    }
486
487    /**
488     * Helper method for setting up the fake audio service.
489     * Calls to the fake audio service need to toggle the return
490     * value of AudioManager#isMicrophoneMute.
491     * @return mock of IAudioService
492     */
493    private IAudioService setupAudioService() {
494        IAudioService audioService = mock(IAudioService.class);
495
496        final AudioManager fakeAudioManager =
497                (AudioManager) mComponentContextFixture.getTestDouble()
498                        .getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
499
500        try {
501            doAnswer(new Answer() {
502                @Override
503                public Object answer(InvocationOnMock i) {
504                    Object[] args = i.getArguments();
505                    doReturn(args[0]).when(fakeAudioManager).isMicrophoneMute();
506                    return null;
507                }
508            }).when(audioService)
509                    .setMicrophoneMute(any(Boolean.class), any(String.class), any(Integer.class));
510
511        } catch (android.os.RemoteException e) {
512            // Do nothing, leave the faked microphone state as-is
513        }
514        return audioService;
515    }
516
517    protected String startOutgoingPhoneCallWithNoPhoneAccount(String number,
518            ConnectionServiceFixture connectionServiceFixture)
519            throws Exception {
520
521        return startOutgoingPhoneCallPendingCreateConnection(number, null,
522                connectionServiceFixture, Process.myUserHandle(), VideoProfile.STATE_AUDIO_ONLY);
523    }
524
525    protected IdPair outgoingCallPhoneAccountSelected(PhoneAccountHandle phoneAccountHandle,
526            int startingNumConnections, int startingNumCalls,
527            ConnectionServiceFixture connectionServiceFixture) throws Exception {
528
529        IdPair ids = outgoingCallCreateConnectionComplete(startingNumConnections, startingNumCalls,
530                phoneAccountHandle, connectionServiceFixture);
531
532        connectionServiceFixture.sendSetDialing(ids.mConnectionId);
533        assertEquals(Call.STATE_DIALING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
534        assertEquals(Call.STATE_DIALING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
535
536        connectionServiceFixture.sendSetVideoState(ids.mConnectionId);
537
538        connectionServiceFixture.sendSetActive(ids.mConnectionId);
539        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
540        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
541
542        return ids;
543    }
544
545    protected IdPair startOutgoingPhoneCall(String number, PhoneAccountHandle phoneAccountHandle,
546            ConnectionServiceFixture connectionServiceFixture, UserHandle initiatingUser)
547            throws Exception {
548
549        return startOutgoingPhoneCall(number, phoneAccountHandle, connectionServiceFixture,
550                initiatingUser, VideoProfile.STATE_AUDIO_ONLY);
551    }
552
553    protected IdPair startOutgoingPhoneCall(String number, PhoneAccountHandle phoneAccountHandle,
554            ConnectionServiceFixture connectionServiceFixture, UserHandle initiatingUser,
555            int videoState) throws Exception {
556        int startingNumConnections = connectionServiceFixture.mConnectionById.size();
557        int startingNumCalls = mInCallServiceFixtureX.mCallById.size();
558
559        startOutgoingPhoneCallPendingCreateConnection(number, phoneAccountHandle,
560                connectionServiceFixture, initiatingUser, videoState);
561
562        return outgoingCallCreateConnectionComplete(startingNumConnections, startingNumCalls,
563                phoneAccountHandle, connectionServiceFixture);
564    }
565
566    protected IdPair triggerEmergencyRedial(PhoneAccountHandle phoneAccountHandle,
567            ConnectionServiceFixture connectionServiceFixture, IdPair emergencyIds)
568            throws Exception {
569        int startingNumConnections = connectionServiceFixture.mConnectionById.size();
570        int startingNumCalls = mInCallServiceFixtureX.mCallById.size();
571
572        // Send the message to disconnect the Emergency call due to an error.
573        // CreateConnectionProcessor should now try the second SIM account
574        connectionServiceFixture.sendSetDisconnected(emergencyIds.mConnectionId,
575                DisconnectCause.ERROR);
576        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
577        assertEquals(Call.STATE_DIALING, mInCallServiceFixtureX.getCall(
578                emergencyIds.mCallId).getState());
579        assertEquals(Call.STATE_DIALING, mInCallServiceFixtureY.getCall(
580                emergencyIds.mCallId).getState());
581
582        return redialingCallCreateConnectionComplete(startingNumConnections, startingNumCalls,
583                phoneAccountHandle, connectionServiceFixture);
584    }
585
586    protected IdPair startOutgoingEmergencyCall(String number,
587            PhoneAccountHandle phoneAccountHandle,
588            ConnectionServiceFixture connectionServiceFixture, UserHandle initiatingUser,
589            int videoState) throws Exception {
590        int startingNumConnections = connectionServiceFixture.mConnectionById.size();
591        int startingNumCalls = mInCallServiceFixtureX.mCallById.size();
592
593        mIsEmergencyCall = true;
594        // Call will not use the ordered broadcaster, since it is an Emergency Call
595        startOutgoingPhoneCallWaitForBroadcaster(number, phoneAccountHandle,
596                connectionServiceFixture, initiatingUser, videoState, true /*isEmergency*/);
597
598        return outgoingCallCreateConnectionComplete(startingNumConnections, startingNumCalls,
599                phoneAccountHandle, connectionServiceFixture);
600    }
601
602    protected void startOutgoingPhoneCallWaitForBroadcaster(String number,
603            PhoneAccountHandle phoneAccountHandle,
604            ConnectionServiceFixture connectionServiceFixture, UserHandle initiatingUser,
605            int videoState, boolean isEmergency) throws Exception {
606        reset(connectionServiceFixture.getTestDouble(), mInCallServiceFixtureX.getTestDouble(),
607                mInCallServiceFixtureY.getTestDouble());
608
609        assertEquals(mInCallServiceFixtureX.mCallById.size(),
610                mInCallServiceFixtureY.mCallById.size());
611        assertEquals((mInCallServiceFixtureX.mInCallAdapter != null),
612                (mInCallServiceFixtureY.mInCallAdapter != null));
613
614        mNumOutgoingCallsMade++;
615
616        boolean hasInCallAdapter = mInCallServiceFixtureX.mInCallAdapter != null;
617
618        Intent actionCallIntent = new Intent();
619        actionCallIntent.setData(Uri.parse("tel:" + number));
620        actionCallIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);
621        if(isEmergency) {
622            actionCallIntent.setAction(Intent.ACTION_CALL_EMERGENCY);
623        } else {
624            actionCallIntent.setAction(Intent.ACTION_CALL);
625        }
626        if (phoneAccountHandle != null) {
627            actionCallIntent.putExtra(
628                    TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
629                    phoneAccountHandle);
630        }
631        if (videoState != VideoProfile.STATE_AUDIO_ONLY) {
632            actionCallIntent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState);
633        }
634
635        final UserHandle userHandle = initiatingUser;
636        Context localAppContext = mComponentContextFixture.getTestDouble().getApplicationContext();
637        new UserCallIntentProcessor(localAppContext, userHandle).processIntent(
638                actionCallIntent, null, true /* hasCallAppOp*/);
639        // UserCallIntentProcessor's mContext.sendBroadcastAsUser(...) will call to an empty method
640        // as to not actually try to send an intent to PrimaryCallReceiver. We verify that it was
641        // called correctly in order to continue.
642        verify(localAppContext).sendBroadcastAsUser(actionCallIntent, UserHandle.SYSTEM);
643        mTelecomSystem.getCallIntentProcessor().processIntent(actionCallIntent);
644        // Wait for handler to start CallerInfo lookup.
645        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
646        // Send the CallerInfo lookup reply.
647        mCallerInfoAsyncQueryFactoryFixture.mRequests.forEach(
648                CallerInfoAsyncQueryFactoryFixture.Request::reply);
649
650        if (!hasInCallAdapter) {
651            verify(mInCallServiceFixtureX.getTestDouble())
652                    .setInCallAdapter(
653                            any(IInCallAdapter.class));
654            verify(mInCallServiceFixtureY.getTestDouble())
655                    .setInCallAdapter(
656                            any(IInCallAdapter.class));
657        }
658    }
659
660    protected String startOutgoingPhoneCallPendingCreateConnection(String number,
661            PhoneAccountHandle phoneAccountHandle,
662            ConnectionServiceFixture connectionServiceFixture, UserHandle initiatingUser,
663            int videoState) throws Exception {
664        startOutgoingPhoneCallWaitForBroadcaster(number,phoneAccountHandle,
665                connectionServiceFixture, initiatingUser, videoState, false /*isEmergency*/);
666
667        ArgumentCaptor<Intent> newOutgoingCallIntent =
668                ArgumentCaptor.forClass(Intent.class);
669        ArgumentCaptor<BroadcastReceiver> newOutgoingCallReceiver =
670                ArgumentCaptor.forClass(BroadcastReceiver.class);
671
672        verify(mComponentContextFixture.getTestDouble().getApplicationContext(),
673                times(mNumOutgoingCallsMade))
674                .sendOrderedBroadcastAsUser(
675                        newOutgoingCallIntent.capture(),
676                        any(UserHandle.class),
677                        anyString(),
678                        anyInt(),
679                        newOutgoingCallReceiver.capture(),
680                        nullable(Handler.class),
681                        anyInt(),
682                        anyString(),
683                        nullable(Bundle.class));
684
685        // Pass on the new outgoing call Intent
686        // Set a dummy PendingResult so the BroadcastReceiver agrees to accept onReceive()
687        newOutgoingCallReceiver.getValue().setPendingResult(
688                new BroadcastReceiver.PendingResult(0, "", null, 0, true, false, null, 0, 0));
689        newOutgoingCallReceiver.getValue().setResultData(
690                newOutgoingCallIntent.getValue().getStringExtra(Intent.EXTRA_PHONE_NUMBER));
691        newOutgoingCallReceiver.getValue().onReceive(mComponentContextFixture.getTestDouble(),
692                newOutgoingCallIntent.getValue());
693
694        return mInCallServiceFixtureX.mLatestCallId;
695    }
696
697    // When Telecom is redialing due to an error, we need to make sure the number of connections
698    // increase, but not the number of Calls in the InCallService.
699    protected IdPair redialingCallCreateConnectionComplete(int startingNumConnections,
700            int startingNumCalls, PhoneAccountHandle phoneAccountHandle,
701            ConnectionServiceFixture connectionServiceFixture) throws Exception {
702
703        assertEquals(startingNumConnections + 1, connectionServiceFixture.mConnectionById.size());
704
705        verify(connectionServiceFixture.getTestDouble())
706                .createConnection(eq(phoneAccountHandle), anyString(), any(ConnectionRequest.class),
707                        eq(false)/*isIncoming*/, anyBoolean(), any());
708        // Wait for handleCreateConnectionComplete
709        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
710
711        // Make sure the number of registered InCallService Calls stays the same.
712        assertEquals(startingNumCalls, mInCallServiceFixtureX.mCallById.size());
713        assertEquals(startingNumCalls, mInCallServiceFixtureY.mCallById.size());
714
715        assertEquals(mInCallServiceFixtureX.mLatestCallId, mInCallServiceFixtureY.mLatestCallId);
716
717        return new IdPair(connectionServiceFixture.mLatestConnectionId,
718                mInCallServiceFixtureX.mLatestCallId);
719    }
720
721    protected IdPair outgoingCallCreateConnectionComplete(int startingNumConnections,
722            int startingNumCalls, PhoneAccountHandle phoneAccountHandle,
723            ConnectionServiceFixture connectionServiceFixture) throws Exception {
724
725        assertEquals(startingNumConnections + 1, connectionServiceFixture.mConnectionById.size());
726
727        verify(connectionServiceFixture.getTestDouble())
728                .createConnection(eq(phoneAccountHandle), anyString(), any(ConnectionRequest.class),
729                        eq(false)/*isIncoming*/, anyBoolean(), any());
730        // Wait for handleCreateConnectionComplete
731        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
732        // Wait for the callback in ConnectionService#onAdapterAttached to execute.
733        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
734
735        assertEquals(startingNumCalls + 1, mInCallServiceFixtureX.mCallById.size());
736        assertEquals(startingNumCalls + 1, mInCallServiceFixtureY.mCallById.size());
737
738        assertEquals(mInCallServiceFixtureX.mLatestCallId, mInCallServiceFixtureY.mLatestCallId);
739
740        return new IdPair(connectionServiceFixture.mLatestConnectionId,
741                mInCallServiceFixtureX.mLatestCallId);
742    }
743
744    protected IdPair startIncomingPhoneCall(
745            String number,
746            PhoneAccountHandle phoneAccountHandle,
747            final ConnectionServiceFixture connectionServiceFixture) throws Exception {
748        return startIncomingPhoneCall(number, phoneAccountHandle, VideoProfile.STATE_AUDIO_ONLY,
749                connectionServiceFixture);
750    }
751
752    protected IdPair startIncomingPhoneCall(
753            String number,
754            PhoneAccountHandle phoneAccountHandle,
755            int videoState,
756            final ConnectionServiceFixture connectionServiceFixture) throws Exception {
757        reset(connectionServiceFixture.getTestDouble(), mInCallServiceFixtureX.getTestDouble(),
758                mInCallServiceFixtureY.getTestDouble());
759
760        assertEquals(mInCallServiceFixtureX.mCallById.size(),
761                mInCallServiceFixtureY.mCallById.size());
762        assertEquals((mInCallServiceFixtureX.mInCallAdapter != null),
763                (mInCallServiceFixtureY.mInCallAdapter != null));
764        final int startingNumConnections = connectionServiceFixture.mConnectionById.size();
765        final int startingNumCalls = mInCallServiceFixtureX.mCallById.size();
766        boolean hasInCallAdapter = mInCallServiceFixtureX.mInCallAdapter != null;
767        connectionServiceFixture.mConnectionServiceDelegate.mVideoState = videoState;
768
769        Bundle extras = new Bundle();
770        extras.putParcelable(
771                TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
772                Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null));
773        mTelecomSystem.getTelecomServiceImpl().getBinder()
774                .addNewIncomingCall(phoneAccountHandle, extras);
775
776        verify(connectionServiceFixture.getTestDouble())
777                .createConnection(any(PhoneAccountHandle.class), anyString(),
778                        any(ConnectionRequest.class), eq(true), eq(false), any());
779
780        // Wait for the handler to start the CallerInfo lookup
781        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
782        // Process the CallerInfo lookup reply
783        mCallerInfoAsyncQueryFactoryFixture.mRequests.forEach(
784                CallerInfoAsyncQueryFactoryFixture.Request::reply);
785
786        IContentProvider blockedNumberProvider =
787                mSpyContext.getContentResolver().acquireProvider(BlockedNumberContract.AUTHORITY);
788        verify(blockedNumberProvider, timeout(TEST_TIMEOUT)).call(
789                anyString(),
790                eq(BlockedNumberContract.SystemContract.METHOD_SHOULD_SYSTEM_BLOCK_NUMBER),
791                eq(number),
792                isNull(Bundle.class));
793
794        // For the case of incoming calls, Telecom connecting the InCall services and adding the
795        // Call is triggered by the async completion of the CallerInfoAsyncQuery. Once the Call
796        // is added, future interactions as triggered by the ConnectionService, through the various
797        // test fixtures, will be synchronous.
798
799        if (!hasInCallAdapter) {
800            verify(mInCallServiceFixtureX.getTestDouble(), timeout(TEST_TIMEOUT))
801                    .setInCallAdapter(any(IInCallAdapter.class));
802            verify(mInCallServiceFixtureY.getTestDouble(), timeout(TEST_TIMEOUT))
803                    .setInCallAdapter(any(IInCallAdapter.class));
804        }
805
806        // Give the InCallService time to respond
807
808        assertTrueWithTimeout(new Predicate<Void>() {
809            @Override
810            public boolean apply(Void v) {
811                return mInCallServiceFixtureX.mInCallAdapter != null;
812            }
813        });
814
815        assertTrueWithTimeout(new Predicate<Void>() {
816            @Override
817            public boolean apply(Void v) {
818                return mInCallServiceFixtureY.mInCallAdapter != null;
819            }
820        });
821
822        verify(mInCallServiceFixtureX.getTestDouble(), timeout(TEST_TIMEOUT))
823                .addCall(any(ParcelableCall.class));
824        verify(mInCallServiceFixtureY.getTestDouble(), timeout(TEST_TIMEOUT))
825                .addCall(any(ParcelableCall.class));
826
827        // Give the InCallService time to respond
828
829        assertTrueWithTimeout(new Predicate<Void>() {
830            @Override
831            public boolean apply(Void v) {
832                return startingNumConnections + 1 ==
833                        connectionServiceFixture.mConnectionById.size();
834            }
835        });
836        assertTrueWithTimeout(new Predicate<Void>() {
837            @Override
838            public boolean apply(Void v) {
839                return startingNumCalls + 1 == mInCallServiceFixtureX.mCallById.size();
840            }
841        });
842        assertTrueWithTimeout(new Predicate<Void>() {
843            @Override
844            public boolean apply(Void v) {
845                return startingNumCalls + 1 == mInCallServiceFixtureY.mCallById.size();
846            }
847        });
848
849        assertEquals(mInCallServiceFixtureX.mLatestCallId, mInCallServiceFixtureY.mLatestCallId);
850
851        return new IdPair(connectionServiceFixture.mLatestConnectionId,
852                mInCallServiceFixtureX.mLatestCallId);
853    }
854
855    protected IdPair startAndMakeActiveOutgoingCall(
856            String number,
857            PhoneAccountHandle phoneAccountHandle,
858            ConnectionServiceFixture connectionServiceFixture) throws Exception {
859        return startAndMakeActiveOutgoingCall(number, phoneAccountHandle, connectionServiceFixture,
860                VideoProfile.STATE_AUDIO_ONLY);
861    }
862
863    // A simple outgoing call, verifying that the appropriate connection service is contacted,
864    // the proper lifecycle is followed, and both In-Call Services are updated correctly.
865    protected IdPair startAndMakeActiveOutgoingCall(
866            String number,
867            PhoneAccountHandle phoneAccountHandle,
868            ConnectionServiceFixture connectionServiceFixture, int videoState) throws Exception {
869        IdPair ids = startOutgoingPhoneCall(number, phoneAccountHandle, connectionServiceFixture,
870                Process.myUserHandle(), videoState);
871
872        connectionServiceFixture.sendSetDialing(ids.mConnectionId);
873        assertEquals(Call.STATE_DIALING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
874        assertEquals(Call.STATE_DIALING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
875
876        connectionServiceFixture.sendSetVideoState(ids.mConnectionId);
877
878        connectionServiceFixture.sendSetActive(ids.mConnectionId);
879        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
880        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
881
882        return ids;
883    }
884
885    protected IdPair startAndMakeActiveIncomingCall(
886            String number,
887            PhoneAccountHandle phoneAccountHandle,
888            ConnectionServiceFixture connectionServiceFixture) throws Exception {
889        return startAndMakeActiveIncomingCall(number, phoneAccountHandle, connectionServiceFixture,
890                VideoProfile.STATE_AUDIO_ONLY);
891    }
892
893    // A simple incoming call, similar in scope to the previous test
894    protected IdPair startAndMakeActiveIncomingCall(
895            String number,
896            PhoneAccountHandle phoneAccountHandle,
897            ConnectionServiceFixture connectionServiceFixture,
898            int videoState) throws Exception {
899        IdPair ids = startIncomingPhoneCall(number, phoneAccountHandle, connectionServiceFixture);
900
901        assertEquals(Call.STATE_RINGING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
902        assertEquals(Call.STATE_RINGING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
903
904        mInCallServiceFixtureX.mInCallAdapter
905                .answerCall(ids.mCallId, videoState);
906
907        if (!VideoProfile.isVideo(videoState)) {
908            verify(connectionServiceFixture.getTestDouble())
909                    .answer(eq(ids.mConnectionId), any());
910        } else {
911            verify(connectionServiceFixture.getTestDouble())
912                    .answerVideo(eq(ids.mConnectionId), eq(videoState), any());
913        }
914
915        connectionServiceFixture.sendSetActive(ids.mConnectionId);
916        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
917        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
918
919        return ids;
920    }
921
922    protected IdPair startAndMakeDialingEmergencyCall(
923            String number,
924            PhoneAccountHandle phoneAccountHandle,
925            ConnectionServiceFixture connectionServiceFixture) throws Exception {
926        IdPair ids = startOutgoingEmergencyCall(number, phoneAccountHandle,
927                connectionServiceFixture, Process.myUserHandle(), VideoProfile.STATE_AUDIO_ONLY);
928
929        connectionServiceFixture.sendSetDialing(ids.mConnectionId);
930        assertEquals(Call.STATE_DIALING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
931        assertEquals(Call.STATE_DIALING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
932
933        return ids;
934    }
935
936    protected static void assertTrueWithTimeout(Predicate<Void> predicate) {
937        int elapsed = 0;
938        while (elapsed < TEST_TIMEOUT) {
939            if (predicate.apply(null)) {
940                return;
941            } else {
942                try {
943                    Thread.sleep(TEST_POLL_INTERVAL);
944                    elapsed += TEST_POLL_INTERVAL;
945                } catch (InterruptedException e) {
946                    fail(e.toString());
947                }
948            }
949        }
950        fail("Timeout in assertTrueWithTimeout");
951    }
952}
953