TelecomSystemTest.java revision a993094840386163e9c2aa65a05e14b49d122318
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.Matchers.any;
21import static org.mockito.Matchers.anyBoolean;
22import static org.mockito.Matchers.anyInt;
23import static org.mockito.Matchers.anyString;
24import static org.mockito.Matchers.eq;
25import static org.mockito.Mockito.doAnswer;
26import static org.mockito.Mockito.doNothing;
27import static org.mockito.Mockito.doReturn;
28import static org.mockito.Mockito.mock;
29import static org.mockito.Mockito.reset;
30import static org.mockito.Mockito.spy;
31import static org.mockito.Mockito.timeout;
32import static org.mockito.Mockito.times;
33import static org.mockito.Mockito.verify;
34
35import android.content.BroadcastReceiver;
36import android.content.ComponentName;
37import android.content.Context;
38import android.content.Intent;
39import android.media.AudioManager;
40import android.media.IAudioService;
41import android.net.Uri;
42import android.os.Bundle;
43import android.os.Debug;
44import android.os.Handler;
45import android.os.Process;
46import android.os.UserHandle;
47import android.telecom.Call;
48import android.telecom.CallAudioState;
49import android.telecom.Connection;
50import android.telecom.ConnectionRequest;
51import android.telecom.DisconnectCause;
52import android.telecom.InCallService;
53import android.telecom.ParcelableCall;
54import android.telecom.PhoneAccount;
55import android.telecom.PhoneAccountHandle;
56import android.telecom.TelecomManager;
57import android.telecom.VideoProfile;
58
59import com.android.internal.telecom.IInCallAdapter;
60import com.android.internal.telecom.IVideoProvider;
61import com.android.internal.util.IndentingPrintWriter;
62import com.android.server.telecom.BluetoothPhoneServiceImpl;
63import com.android.server.telecom.CallAudioManager;
64import com.android.server.telecom.CallIntentProcessor;
65import com.android.server.telecom.CallerInfoAsyncQueryFactory;
66import com.android.server.telecom.CallsManager;
67import com.android.server.telecom.CallsManagerListenerBase;
68import com.android.server.telecom.ContactsAsyncHelper;
69import com.android.server.telecom.HeadsetMediaButton;
70import com.android.server.telecom.HeadsetMediaButtonFactory;
71import com.android.server.telecom.InCallWakeLockController;
72import com.android.server.telecom.InCallWakeLockControllerFactory;
73import com.android.server.telecom.Log;
74import com.android.server.telecom.MissedCallNotifier;
75import com.android.server.telecom.PhoneAccountRegistrar;
76import com.android.server.telecom.ProximitySensorManager;
77import com.android.server.telecom.ProximitySensorManagerFactory;
78import com.android.server.telecom.TelecomSystem;
79import com.android.server.telecom.components.PrimaryCallReceiver;
80import com.android.server.telecom.components.UserCallIntentProcessor;
81import com.android.server.telecom.ui.MissedCallNotifierImpl;
82import com.android.server.telecom.ui.MissedCallNotifierImpl.MissedCallNotifierImplFactory;
83
84import com.google.common.base.Predicate;
85
86import org.mockito.ArgumentCaptor;
87import org.mockito.Mock;
88import org.mockito.Mockito;
89import org.mockito.invocation.InvocationOnMock;
90import org.mockito.stubbing.Answer;
91
92import java.io.StringWriter;
93import java.util.Map;
94import java.util.concurrent.BrokenBarrierException;
95import java.util.concurrent.CountDownLatch;
96import java.util.concurrent.CyclicBarrier;
97
98/**
99 * Implements mocks and functionality required to implement telecom system tests.
100 */
101public class TelecomSystemTest extends TelecomTestCase {
102
103    static final int TEST_POLL_INTERVAL = 10;  // milliseconds
104    static final int TEST_TIMEOUT = 1000;  // milliseconds
105
106    public class HeadsetMediaButtonFactoryF implements HeadsetMediaButtonFactory  {
107        @Override
108        public HeadsetMediaButton create(Context context, CallsManager callsManager,
109                TelecomSystem.SyncRoot lock) {
110            return mHeadsetMediaButton;
111        }
112    }
113
114    public class ProximitySensorManagerFactoryF implements ProximitySensorManagerFactory {
115        @Override
116        public ProximitySensorManager create(Context context, CallsManager callsManager) {
117            return mProximitySensorManager;
118        }
119    }
120
121    public class InCallWakeLockControllerFactoryF implements InCallWakeLockControllerFactory {
122        @Override
123        public InCallWakeLockController create(Context context, CallsManager callsManager) {
124            return mInCallWakeLockController;
125        }
126    }
127
128    public static class MissedCallNotifierFakeImpl extends CallsManagerListenerBase
129            implements MissedCallNotifier {
130        @Override
131        public void clearMissedCalls(UserHandle userHandle) {
132
133        }
134
135        @Override
136        public void showMissedCallNotification(com.android.server.telecom.Call call) {
137
138        }
139
140        @Override
141        public void reloadFromDatabase(TelecomSystem.SyncRoot lock, CallsManager callsManager,
142                ContactsAsyncHelper contactsAsyncHelper,
143                CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory, UserHandle userHandle) {
144
145        }
146
147        @Override
148        public void setCurrentUserHandle(UserHandle userHandle) {
149
150        }
151    }
152
153    MissedCallNotifier mMissedCallNotifier = new MissedCallNotifierFakeImpl();
154    @Mock HeadsetMediaButton mHeadsetMediaButton;
155    @Mock ProximitySensorManager mProximitySensorManager;
156    @Mock InCallWakeLockController mInCallWakeLockController;
157    @Mock BluetoothPhoneServiceImpl mBluetoothPhoneServiceImpl;
158
159    final ComponentName mInCallServiceComponentNameX =
160            new ComponentName(
161                    "incall-service-package-X",
162                    "incall-service-class-X");
163    final ComponentName mInCallServiceComponentNameY =
164            new ComponentName(
165                    "incall-service-package-Y",
166                    "incall-service-class-Y");
167
168    InCallServiceFixture mInCallServiceFixtureX;
169    InCallServiceFixture mInCallServiceFixtureY;
170
171    final ComponentName mConnectionServiceComponentNameA =
172            new ComponentName(
173                    "connection-service-package-A",
174                    "connection-service-class-A");
175    final ComponentName mConnectionServiceComponentNameB =
176            new ComponentName(
177                    "connection-service-package-B",
178                    "connection-service-class-B");
179
180    final PhoneAccount mPhoneAccountA0 =
181            PhoneAccount.builder(
182                    new PhoneAccountHandle(
183                            mConnectionServiceComponentNameA,
184                            "id A 0"),
185                    "Phone account service A ID 0")
186                    .addSupportedUriScheme("tel")
187                    .setCapabilities(
188                            PhoneAccount.CAPABILITY_CALL_PROVIDER |
189                            PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
190                    .build();
191    final PhoneAccount mPhoneAccountA1 =
192            PhoneAccount.builder(
193                    new PhoneAccountHandle(
194                            mConnectionServiceComponentNameA,
195                            "id A 1"),
196                    "Phone account service A ID 1")
197                    .addSupportedUriScheme("tel")
198                    .setCapabilities(
199                            PhoneAccount.CAPABILITY_CALL_PROVIDER |
200                            PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
201                    .build();
202    final PhoneAccount mPhoneAccountB0 =
203            PhoneAccount.builder(
204                    new PhoneAccountHandle(
205                            mConnectionServiceComponentNameB,
206                            "id B 0"),
207                    "Phone account service B ID 0")
208                    .addSupportedUriScheme("tel")
209                    .setCapabilities(
210                            PhoneAccount.CAPABILITY_CALL_PROVIDER |
211                            PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
212                    .build();
213
214    ConnectionServiceFixture mConnectionServiceFixtureA;
215    ConnectionServiceFixture mConnectionServiceFixtureB;
216
217    CallerInfoAsyncQueryFactoryFixture mCallerInfoAsyncQueryFactoryFixture;
218
219    IAudioService mAudioService;
220
221    TelecomSystem mTelecomSystem;
222
223    Context mSpyContext;
224
225    private int mNumOutgoingCallsMade;
226
227    class IdPair {
228        final String mConnectionId;
229        final String mCallId;
230
231        public IdPair(String connectionId, String callId) {
232            this.mConnectionId = connectionId;
233            this.mCallId = callId;
234        }
235    }
236
237    @Override
238    public void setUp() throws Exception {
239        super.setUp();
240        mSpyContext = mComponentContextFixture.getTestDouble().getApplicationContext();
241        doReturn(mSpyContext).when(mSpyContext).getApplicationContext();
242
243        mNumOutgoingCallsMade = 0;
244
245        // First set up information about the In-Call services in the mock Context, since
246        // Telecom will search for these as soon as it is instantiated
247        setupInCallServices();
248
249        // Next, create the TelecomSystem, our system under test
250        setupTelecomSystem();
251
252        // Finally, register the ConnectionServices with the PhoneAccountRegistrar of the
253        // now-running TelecomSystem
254        setupConnectionServices();
255    }
256
257    @Override
258    public void tearDown() throws Exception {
259        mTelecomSystem = null;
260        super.tearDown();
261    }
262
263    private void setupTelecomSystem() throws Exception {
264        // Use actual implementations instead of mocking the interface out.
265        HeadsetMediaButtonFactory headsetMediaButtonFactory =
266                spy(new HeadsetMediaButtonFactoryF());
267        ProximitySensorManagerFactory proximitySensorManagerFactory =
268                spy(new ProximitySensorManagerFactoryF());
269        InCallWakeLockControllerFactory inCallWakeLockControllerFactory =
270                spy(new InCallWakeLockControllerFactoryF());
271        mAudioService = setupAudioService();
272
273        mCallerInfoAsyncQueryFactoryFixture = new CallerInfoAsyncQueryFactoryFixture();
274
275        mTelecomSystem = new TelecomSystem(
276                mComponentContextFixture.getTestDouble(),
277                new MissedCallNotifierImplFactory() {
278                    @Override
279                    public MissedCallNotifier makeMissedCallNotifierImpl(Context context,
280                            PhoneAccountRegistrar phoneAccountRegistrar) {
281                        return mMissedCallNotifier;
282                    }
283                },
284                mCallerInfoAsyncQueryFactoryFixture.getTestDouble(),
285                headsetMediaButtonFactory,
286                proximitySensorManagerFactory,
287                inCallWakeLockControllerFactory,
288                new CallAudioManager.AudioServiceFactory() {
289                    @Override
290                    public IAudioService getAudioService() {
291                        return mAudioService;
292                    }
293                },
294                new BluetoothPhoneServiceImpl.BluetoothPhoneServiceImplFactory() {
295                    @Override
296                    public BluetoothPhoneServiceImpl makeBluetoothPhoneServiceImpl(Context context,
297                            TelecomSystem.SyncRoot lock, CallsManager callsManager,
298                            PhoneAccountRegistrar phoneAccountRegistrar) {
299                        return mBluetoothPhoneServiceImpl;
300                    }
301                });
302
303        mComponentContextFixture.setTelecomManager(new TelecomManager(
304                mComponentContextFixture.getTestDouble(),
305                mTelecomSystem.getTelecomServiceImpl().getBinder()));
306
307        verify(headsetMediaButtonFactory).create(
308                eq(mComponentContextFixture.getTestDouble().getApplicationContext()),
309                any(CallsManager.class),
310                any(TelecomSystem.SyncRoot.class));
311        verify(proximitySensorManagerFactory).create(
312                eq(mComponentContextFixture.getTestDouble().getApplicationContext()),
313                any(CallsManager.class));
314        verify(inCallWakeLockControllerFactory).create(
315                eq(mComponentContextFixture.getTestDouble().getApplicationContext()),
316                any(CallsManager.class));
317    }
318
319    private void setupConnectionServices() throws Exception {
320        mConnectionServiceFixtureA = new ConnectionServiceFixture();
321        mConnectionServiceFixtureB = new ConnectionServiceFixture();
322
323        mComponentContextFixture.addConnectionService(mConnectionServiceComponentNameA,
324                mConnectionServiceFixtureA.getTestDouble());
325        mComponentContextFixture.addConnectionService(mConnectionServiceComponentNameB,
326                mConnectionServiceFixtureB.getTestDouble());
327
328        mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountA0);
329        mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountA1);
330        mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountB0);
331
332        mTelecomSystem.getPhoneAccountRegistrar().setUserSelectedOutgoingPhoneAccount(
333                mPhoneAccountA0.getAccountHandle(), Process.myUserHandle());
334    }
335
336    private void setupInCallServices() throws Exception {
337        mComponentContextFixture.putResource(
338                com.android.server.telecom.R.string.ui_default_package,
339                mInCallServiceComponentNameX.getPackageName());
340        mComponentContextFixture.putResource(
341                com.android.server.telecom.R.string.incall_default_class,
342                mInCallServiceComponentNameX.getClassName());
343        mComponentContextFixture.putBooleanResource(
344                com.android.internal.R.bool.config_voice_capable, true);
345
346        mInCallServiceFixtureX = new InCallServiceFixture();
347        mInCallServiceFixtureY = new InCallServiceFixture();
348
349        mComponentContextFixture.addInCallService(mInCallServiceComponentNameX,
350                mInCallServiceFixtureX.getTestDouble());
351        mComponentContextFixture.addInCallService(mInCallServiceComponentNameY,
352                mInCallServiceFixtureY.getTestDouble());
353    }
354
355    /**
356     * Helper method for setting up the fake audio service.
357     * Calls to the fake audio service need to toggle the return
358     * value of AudioManager#isMicrophoneMute.
359     * @return mock of IAudioService
360     */
361    private IAudioService setupAudioService() {
362        IAudioService audioService = mock(IAudioService.class);
363
364        final AudioManager fakeAudioManager =
365                (AudioManager) mComponentContextFixture.getTestDouble()
366                        .getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
367
368        try {
369            doAnswer(new Answer() {
370                @Override
371                public Object answer(InvocationOnMock i) {
372                    Object[] args = i.getArguments();
373                    doReturn(args[0]).when(fakeAudioManager).isMicrophoneMute();
374                    return null;
375                }
376            }).when(audioService)
377                    .setMicrophoneMute(any(Boolean.class), any(String.class), any(Integer.class));
378
379        } catch (android.os.RemoteException e) {
380            // Do nothing, leave the faked microphone state as-is
381        }
382        return audioService;
383    }
384
385    protected IdPair startOutgoingPhoneCall(String number, PhoneAccountHandle phoneAccountHandle,
386            ConnectionServiceFixture connectionServiceFixture, UserHandle initiatingUser)
387            throws Exception {
388        return startOutgoingPhoneCall(number, phoneAccountHandle, connectionServiceFixture,
389                initiatingUser, VideoProfile.STATE_AUDIO_ONLY);
390    }
391
392    protected IdPair startOutgoingPhoneCall(String number, PhoneAccountHandle phoneAccountHandle,
393            ConnectionServiceFixture connectionServiceFixture, UserHandle initiatingUser,
394            int videoState) throws Exception {
395        reset(connectionServiceFixture.getTestDouble(), mInCallServiceFixtureX.getTestDouble(),
396                mInCallServiceFixtureY.getTestDouble());
397
398        assertEquals(mInCallServiceFixtureX.mCallById.size(),
399                mInCallServiceFixtureY.mCallById.size());
400        assertEquals((mInCallServiceFixtureX.mInCallAdapter != null),
401                (mInCallServiceFixtureY.mInCallAdapter != null));
402
403        mNumOutgoingCallsMade++;
404        int startingNumConnections = connectionServiceFixture.mConnectionById.size();
405        int startingNumCalls = mInCallServiceFixtureX.mCallById.size();
406        boolean hasInCallAdapter = mInCallServiceFixtureX.mInCallAdapter != null;
407
408        Intent actionCallIntent = new Intent();
409        actionCallIntent.setData(Uri.parse("tel:" + number));
410        actionCallIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);
411        actionCallIntent.setAction(Intent.ACTION_CALL);
412        if (phoneAccountHandle != null) {
413            actionCallIntent.putExtra(
414                    TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
415                    phoneAccountHandle);
416        }
417        if (videoState != VideoProfile.STATE_AUDIO_ONLY) {
418            actionCallIntent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState);
419        }
420
421        final UserHandle userHandle = initiatingUser;
422        Context localAppContext = mComponentContextFixture.getTestDouble().getApplicationContext();
423        new UserCallIntentProcessor(localAppContext, userHandle).processIntent(
424                actionCallIntent, null, true /* hasCallAppOp*/);
425        // UserCallIntentProcessor's mContext.sendBroadcastAsUser(...) will call to an empty method
426        // as to not actually try to send an intent to PrimaryCallReceiver. We verify that it was
427        // called correctly in order to continue.
428        verify(localAppContext).sendBroadcastAsUser(actionCallIntent, UserHandle.SYSTEM);
429        mTelecomSystem.getCallIntentProcessor().processIntent(actionCallIntent);
430
431        if (!hasInCallAdapter) {
432            verify(mInCallServiceFixtureX.getTestDouble())
433                    .setInCallAdapter(
434                            any(IInCallAdapter.class));
435            verify(mInCallServiceFixtureY.getTestDouble())
436                    .setInCallAdapter(
437                            any(IInCallAdapter.class));
438        }
439
440        ArgumentCaptor<Intent> newOutgoingCallIntent =
441                ArgumentCaptor.forClass(Intent.class);
442        ArgumentCaptor<BroadcastReceiver> newOutgoingCallReceiver =
443                ArgumentCaptor.forClass(BroadcastReceiver.class);
444
445        verify(mComponentContextFixture.getTestDouble().getApplicationContext(),
446                times(mNumOutgoingCallsMade))
447                .sendOrderedBroadcastAsUser(
448                        newOutgoingCallIntent.capture(),
449                        any(UserHandle.class),
450                        anyString(),
451                        anyInt(),
452                        newOutgoingCallReceiver.capture(),
453                        any(Handler.class),
454                        anyInt(),
455                        anyString(),
456                        any(Bundle.class));
457
458        // Pass on the new outgoing call Intent
459        // Set a dummy PendingResult so the BroadcastReceiver agrees to accept onReceive()
460        newOutgoingCallReceiver.getValue().setPendingResult(
461                new BroadcastReceiver.PendingResult(0, "", null, 0, true, false, null, 0, 0));
462        newOutgoingCallReceiver.getValue().setResultData(
463                newOutgoingCallIntent.getValue().getStringExtra(Intent.EXTRA_PHONE_NUMBER));
464        newOutgoingCallReceiver.getValue().onReceive(mComponentContextFixture.getTestDouble(),
465                newOutgoingCallIntent.getValue());
466
467        assertEquals(startingNumConnections + 1, connectionServiceFixture.mConnectionById.size());
468
469        verify(connectionServiceFixture.getTestDouble())
470                .createConnection(eq(phoneAccountHandle), anyString(), any(ConnectionRequest.class),
471                        anyBoolean(), anyBoolean());
472        connectionServiceFixture.sendHandleCreateConnectionComplete(
473                connectionServiceFixture.mLatestConnectionId);
474
475        assertEquals(startingNumCalls + 1, mInCallServiceFixtureX.mCallById.size());
476        assertEquals(startingNumCalls + 1, mInCallServiceFixtureY.mCallById.size());
477
478        assertEquals(mInCallServiceFixtureX.mLatestCallId, mInCallServiceFixtureY.mLatestCallId);
479
480        return new IdPair(connectionServiceFixture.mLatestConnectionId,
481                mInCallServiceFixtureX.mLatestCallId);
482    }
483
484    protected IdPair startIncomingPhoneCall(
485            String number,
486            PhoneAccountHandle phoneAccountHandle,
487            final ConnectionServiceFixture connectionServiceFixture) throws Exception {
488        return startIncomingPhoneCall(number, phoneAccountHandle, VideoProfile.STATE_AUDIO_ONLY,
489                connectionServiceFixture);
490    }
491
492    protected IdPair startIncomingPhoneCall(
493            String number,
494            PhoneAccountHandle phoneAccountHandle,
495            int videoState,
496            final ConnectionServiceFixture connectionServiceFixture) throws Exception {
497        reset(connectionServiceFixture.getTestDouble(), mInCallServiceFixtureX.getTestDouble(),
498                mInCallServiceFixtureY.getTestDouble());
499
500        assertEquals(mInCallServiceFixtureX.mCallById.size(),
501                mInCallServiceFixtureY.mCallById.size());
502        assertEquals((mInCallServiceFixtureX.mInCallAdapter != null),
503                (mInCallServiceFixtureY.mInCallAdapter != null));
504        final int startingNumConnections = connectionServiceFixture.mConnectionById.size();
505        final int startingNumCalls = mInCallServiceFixtureX.mCallById.size();
506        boolean hasInCallAdapter = mInCallServiceFixtureX.mInCallAdapter != null;
507
508        Bundle extras = new Bundle();
509        extras.putParcelable(
510                TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
511                Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null));
512        mTelecomSystem.getTelecomServiceImpl().getBinder()
513                .addNewIncomingCall(phoneAccountHandle, extras);
514
515        verify(connectionServiceFixture.getTestDouble())
516                .createConnection(any(PhoneAccountHandle.class), anyString(),
517                        any(ConnectionRequest.class), eq(true), eq(false));
518
519        mConnectionServiceFixtureA.mConnectionById.get(
520                connectionServiceFixture.mLatestConnectionId).videoState = videoState;
521
522        connectionServiceFixture.sendHandleCreateConnectionComplete(
523                connectionServiceFixture.mLatestConnectionId);
524        connectionServiceFixture.sendSetRinging(connectionServiceFixture.mLatestConnectionId);
525        connectionServiceFixture.sendSetVideoState(connectionServiceFixture.mLatestConnectionId);
526
527        // For the case of incoming calls, Telecom connecting the InCall services and adding the
528        // Call is triggered by the async completion of the CallerInfoAsyncQuery. Once the Call
529        // is added, future interactions as triggered by the ConnectionService, through the various
530        // test fixtures, will be synchronous.
531
532        if (!hasInCallAdapter) {
533            verify(mInCallServiceFixtureX.getTestDouble(), timeout(TEST_TIMEOUT))
534                    .setInCallAdapter(any(IInCallAdapter.class));
535            verify(mInCallServiceFixtureY.getTestDouble(), timeout(TEST_TIMEOUT))
536                    .setInCallAdapter(any(IInCallAdapter.class));
537        }
538
539        // Give the InCallService time to respond
540
541        assertTrueWithTimeout(new Predicate<Void>() {
542            @Override
543            public boolean apply(Void v) {
544                return mInCallServiceFixtureX.mInCallAdapter != null;
545            }
546        });
547
548        assertTrueWithTimeout(new Predicate<Void>() {
549            @Override
550            public boolean apply(Void v) {
551                return mInCallServiceFixtureY.mInCallAdapter != null;
552            }
553        });
554
555        verify(mInCallServiceFixtureX.getTestDouble(), timeout(TEST_TIMEOUT))
556                .addCall(any(ParcelableCall.class));
557        verify(mInCallServiceFixtureY.getTestDouble(), timeout(TEST_TIMEOUT))
558                .addCall(any(ParcelableCall.class));
559
560        // Give the InCallService time to respond
561
562        assertTrueWithTimeout(new Predicate<Void>() {
563            @Override
564            public boolean apply(Void v) {
565                return startingNumConnections + 1 ==
566                        connectionServiceFixture.mConnectionById.size();
567            }
568        });
569        assertTrueWithTimeout(new Predicate<Void>() {
570            @Override
571            public boolean apply(Void v) {
572                return startingNumCalls + 1 == mInCallServiceFixtureX.mCallById.size();
573            }
574        });
575        assertTrueWithTimeout(new Predicate<Void>() {
576            @Override
577            public boolean apply(Void v) {
578                return startingNumCalls + 1 == mInCallServiceFixtureY.mCallById.size();
579            }
580        });
581
582        assertEquals(mInCallServiceFixtureX.mLatestCallId, mInCallServiceFixtureY.mLatestCallId);
583
584        return new IdPair(connectionServiceFixture.mLatestConnectionId,
585                mInCallServiceFixtureX.mLatestCallId);
586    }
587
588    protected IdPair startAndMakeActiveOutgoingCall(
589            String number,
590            PhoneAccountHandle phoneAccountHandle,
591            ConnectionServiceFixture connectionServiceFixture) throws Exception {
592        return startAndMakeActiveOutgoingCall(number, phoneAccountHandle, connectionServiceFixture,
593                VideoProfile.STATE_AUDIO_ONLY);
594    }
595
596    // A simple outgoing call, verifying that the appropriate connection service is contacted,
597    // the proper lifecycle is followed, and both In-Call Services are updated correctly.
598    protected IdPair startAndMakeActiveOutgoingCall(
599            String number,
600            PhoneAccountHandle phoneAccountHandle,
601            ConnectionServiceFixture connectionServiceFixture, int videoState) throws Exception {
602        IdPair ids = startOutgoingPhoneCall(number, phoneAccountHandle, connectionServiceFixture,
603                Process.myUserHandle(), videoState);
604
605        connectionServiceFixture.sendSetDialing(ids.mConnectionId);
606        assertEquals(Call.STATE_DIALING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
607        assertEquals(Call.STATE_DIALING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
608
609        connectionServiceFixture.sendSetVideoState(ids.mConnectionId);
610
611        connectionServiceFixture.sendSetActive(ids.mConnectionId);
612        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
613        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
614
615        return ids;
616    }
617
618    protected IdPair startAndMakeActiveIncomingCall(
619            String number,
620            PhoneAccountHandle phoneAccountHandle,
621            ConnectionServiceFixture connectionServiceFixture) throws Exception {
622        return startAndMakeActiveIncomingCall(number, phoneAccountHandle, connectionServiceFixture,
623                VideoProfile.STATE_AUDIO_ONLY);
624    }
625
626    // A simple incoming call, similar in scope to the previous test
627    protected IdPair startAndMakeActiveIncomingCall(
628            String number,
629            PhoneAccountHandle phoneAccountHandle,
630            ConnectionServiceFixture connectionServiceFixture,
631            int videoState) throws Exception {
632        IdPair ids = startIncomingPhoneCall(number, phoneAccountHandle, connectionServiceFixture);
633
634        assertEquals(Call.STATE_RINGING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
635        assertEquals(Call.STATE_RINGING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
636
637        mInCallServiceFixtureX.mInCallAdapter
638                .answerCall(ids.mCallId, videoState);
639
640        if (!VideoProfile.isVideo(videoState)) {
641            verify(connectionServiceFixture.getTestDouble())
642                    .answer(ids.mConnectionId);
643        } else {
644            verify(connectionServiceFixture.getTestDouble())
645                    .answerVideo(ids.mConnectionId, videoState);
646        }
647
648        connectionServiceFixture.sendSetActive(ids.mConnectionId);
649        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
650        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
651
652        return ids;
653    }
654
655    protected static void assertTrueWithTimeout(Predicate<Void> predicate) {
656        int elapsed = 0;
657        while (elapsed < TEST_TIMEOUT) {
658            if (predicate.apply(null)) {
659                return;
660            } else {
661                try {
662                    Thread.sleep(TEST_POLL_INTERVAL);
663                    elapsed += TEST_POLL_INTERVAL;
664                } catch (InterruptedException e) {
665                    fail(e.toString());
666                }
667            }
668        }
669        fail("Timeout in assertTrueWithTimeout");
670    }
671}
672