TelecomSystemTest.java revision f7783fb45c262a36d813cb450062d0076490a530
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.doReturn;
27import static org.mockito.Mockito.mock;
28import static org.mockito.Mockito.reset;
29import static org.mockito.Mockito.timeout;
30import static org.mockito.Mockito.times;
31import static org.mockito.Mockito.verify;
32import static org.mockito.Mockito.when;
33
34import android.content.BroadcastReceiver;
35import android.content.ComponentName;
36import android.content.Context;
37import android.content.Intent;
38import android.media.AudioManager;
39import android.media.IAudioService;
40import android.net.Uri;
41import android.os.Bundle;
42import android.os.Handler;
43import android.os.UserHandle;
44import android.telecom.Call;
45import android.telecom.CallAudioState;
46import android.telecom.Connection;
47import android.telecom.ConnectionRequest;
48import android.telecom.DisconnectCause;
49import android.telecom.ParcelableCall;
50import android.telecom.PhoneAccount;
51import android.telecom.PhoneAccountHandle;
52import android.telecom.TelecomManager;
53import android.telecom.VideoProfile;
54
55import com.android.internal.telecom.IInCallAdapter;
56import com.android.server.telecom.BluetoothPhoneServiceImpl;
57import com.android.server.telecom.CallAudioManager;
58import com.android.server.telecom.CallsManager;
59import com.android.server.telecom.HeadsetMediaButton;
60import com.android.server.telecom.HeadsetMediaButtonFactory;
61import com.android.server.telecom.InCallWakeLockController;
62import com.android.server.telecom.InCallWakeLockControllerFactory;
63import com.android.server.telecom.Log;
64import com.android.server.telecom.MissedCallNotifier;
65import com.android.server.telecom.PhoneAccountRegistrar;
66import com.android.server.telecom.ProximitySensorManager;
67import com.android.server.telecom.ProximitySensorManagerFactory;
68import com.android.server.telecom.TelecomSystem;
69
70import com.google.common.base.Predicate;
71
72import org.mockito.ArgumentCaptor;
73import org.mockito.Mock;
74import org.mockito.invocation.InvocationOnMock;
75import org.mockito.stubbing.Answer;
76
77import java.util.concurrent.BrokenBarrierException;
78import java.util.concurrent.CountDownLatch;
79import java.util.concurrent.CyclicBarrier;
80
81public class TelecomSystemTest extends TelecomTestCase {
82
83    static final int TEST_POLL_INTERVAL = 10;  // milliseconds
84    static final int TEST_TIMEOUT = 1000;  // milliseconds
85
86    @Mock MissedCallNotifier mMissedCallNotifier;
87    @Mock HeadsetMediaButton mHeadsetMediaButton;
88    @Mock ProximitySensorManager mProximitySensorManager;
89    @Mock InCallWakeLockController mInCallWakeLockController;
90    @Mock BluetoothPhoneServiceImpl mBluetoothPhoneServiceImpl;
91
92    final ComponentName mInCallServiceComponentNameX =
93            new ComponentName(
94                    "incall-service-package-X",
95                    "incall-service-class-X");
96    final ComponentName mInCallServiceComponentNameY =
97            new ComponentName(
98                    "incall-service-package-Y",
99                    "incall-service-class-Y");
100
101    InCallServiceFixture mInCallServiceFixtureX;
102    InCallServiceFixture mInCallServiceFixtureY;
103
104    final ComponentName mConnectionServiceComponentNameA =
105            new ComponentName(
106                    "connection-service-package-A",
107                    "connection-service-class-A");
108    final ComponentName mConnectionServiceComponentNameB =
109            new ComponentName(
110                    "connection-service-package-B",
111                    "connection-service-class-B");
112
113    final PhoneAccount mPhoneAccountA0 =
114            PhoneAccount.builder(
115                    new PhoneAccountHandle(
116                            mConnectionServiceComponentNameA,
117                            "id A 0"),
118                    "Phone account service A ID 0")
119                    .addSupportedUriScheme("tel")
120                    .setCapabilities(
121                            PhoneAccount.CAPABILITY_CALL_PROVIDER |
122                            PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
123                    .build();
124    final PhoneAccount mPhoneAccountA1 =
125            PhoneAccount.builder(
126                    new PhoneAccountHandle(
127                            mConnectionServiceComponentNameA,
128                            "id A 1"),
129                    "Phone account service A ID 1")
130                    .addSupportedUriScheme("tel")
131                    .setCapabilities(
132                            PhoneAccount.CAPABILITY_CALL_PROVIDER |
133                            PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
134                    .build();
135    final PhoneAccount mPhoneAccountB0 =
136            PhoneAccount.builder(
137                    new PhoneAccountHandle(
138                            mConnectionServiceComponentNameB,
139                            "id B 0"),
140                    "Phone account service B ID 0")
141                    .addSupportedUriScheme("tel")
142                    .setCapabilities(
143                            PhoneAccount.CAPABILITY_CALL_PROVIDER |
144                            PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
145                    .build();
146
147    ConnectionServiceFixture mConnectionServiceFixtureA;
148    ConnectionServiceFixture mConnectionServiceFixtureB;
149
150    CallerInfoAsyncQueryFactoryFixture mCallerInfoAsyncQueryFactoryFixture;
151
152    IAudioService mAudioService;
153
154    TelecomSystem mTelecomSystem;
155
156    private int mNumOutgoingCallsMade;
157
158    class IdPair {
159        final String mConnectionId;
160        final String mCallId;
161
162        public IdPair(String connectionId, String callId) {
163            this.mConnectionId = connectionId;
164            this.mCallId = callId;
165        }
166    }
167
168    @Override
169    public void setUp() throws Exception {
170        super.setUp();
171        mNumOutgoingCallsMade = 0;
172
173        // First set up information about the In-Call services in the mock Context, since
174        // Telecom will search for these as soon as it is instantiated
175        setupInCallServices();
176
177        // Next, create the TelecomSystem, our system under test
178        setupTelecomSystem();
179
180        // Finally, register the ConnectionServices with the PhoneAccountRegistrar of the
181        // now-running TelecomSystem
182        setupConnectionServices();
183    }
184
185    @Override
186    public void tearDown() throws Exception {
187        mTelecomSystem = null;
188        super.tearDown();
189    }
190
191    private void setupTelecomSystem() throws Exception {
192        HeadsetMediaButtonFactory headsetMediaButtonFactory =
193                mock(HeadsetMediaButtonFactory.class);
194        ProximitySensorManagerFactory proximitySensorManagerFactory =
195                mock(ProximitySensorManagerFactory.class);
196        InCallWakeLockControllerFactory inCallWakeLockControllerFactory =
197                mock(InCallWakeLockControllerFactory.class);
198        mAudioService = setupAudioService();
199
200        mCallerInfoAsyncQueryFactoryFixture = new CallerInfoAsyncQueryFactoryFixture();
201
202        when(headsetMediaButtonFactory.create(
203                any(Context.class),
204                any(CallsManager.class),
205                any(TelecomSystem.SyncRoot.class)))
206                .thenReturn(mHeadsetMediaButton);
207        when(proximitySensorManagerFactory.create(
208                any(Context.class),
209                any(CallsManager.class)))
210                .thenReturn(mProximitySensorManager);
211        when(inCallWakeLockControllerFactory.create(
212                any(Context.class),
213                any(CallsManager.class)))
214                .thenReturn(mInCallWakeLockController);
215
216        mTelecomSystem = new TelecomSystem(
217                mComponentContextFixture.getTestDouble(),
218                mMissedCallNotifier,
219                mCallerInfoAsyncQueryFactoryFixture.getTestDouble(),
220                headsetMediaButtonFactory,
221                proximitySensorManagerFactory,
222                inCallWakeLockControllerFactory,
223                new CallAudioManager.AudioServiceFactory() {
224                    @Override
225                    public IAudioService getAudioService() {
226                        return mAudioService;
227                    }
228                },
229                new BluetoothPhoneServiceImpl.BluetoothPhoneServiceImplFactory() {
230                    @Override
231                    public BluetoothPhoneServiceImpl makeBluetoothPhoneServiceImpl(Context context,
232                            TelecomSystem.SyncRoot lock, CallsManager callsManager,
233                            PhoneAccountRegistrar phoneAccountRegistrar) {
234                        return mBluetoothPhoneServiceImpl;
235                    }
236                });
237
238        mComponentContextFixture.setTelecomManager(new TelecomManager(
239                mComponentContextFixture.getTestDouble(),
240                mTelecomSystem.getTelecomServiceImpl().getBinder()));
241
242        verify(headsetMediaButtonFactory).create(
243                eq(mComponentContextFixture.getTestDouble().getApplicationContext()),
244                any(CallsManager.class),
245                any(TelecomSystem.SyncRoot.class));
246        verify(proximitySensorManagerFactory).create(
247                eq(mComponentContextFixture.getTestDouble().getApplicationContext()),
248                any(CallsManager.class));
249        verify(inCallWakeLockControllerFactory).create(
250                eq(mComponentContextFixture.getTestDouble().getApplicationContext()),
251                any(CallsManager.class));
252    }
253
254    private void setupConnectionServices() throws Exception {
255        mConnectionServiceFixtureA = new ConnectionServiceFixture();
256        mConnectionServiceFixtureB = new ConnectionServiceFixture();
257
258        mComponentContextFixture.addConnectionService(
259                mConnectionServiceComponentNameA,
260                mConnectionServiceFixtureA.getTestDouble());
261        mComponentContextFixture.addConnectionService(
262                mConnectionServiceComponentNameB,
263                mConnectionServiceFixtureB.getTestDouble());
264
265        mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountA0);
266        mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountA1);
267        mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountB0);
268
269        mTelecomSystem.getPhoneAccountRegistrar().setUserSelectedOutgoingPhoneAccount(
270                mPhoneAccountA0.getAccountHandle());
271    }
272
273    private void setupInCallServices() throws Exception {
274        mComponentContextFixture.putResource(
275                com.android.server.telecom.R.string.ui_default_package,
276                mInCallServiceComponentNameX.getPackageName());
277        mComponentContextFixture.putResource(
278                com.android.server.telecom.R.string.incall_default_class,
279                mInCallServiceComponentNameX.getClassName());
280
281        mInCallServiceFixtureX = new InCallServiceFixture();
282        mInCallServiceFixtureY = new InCallServiceFixture();
283
284        mComponentContextFixture.addInCallService(
285                mInCallServiceComponentNameX,
286                mInCallServiceFixtureX.getTestDouble());
287        mComponentContextFixture.addInCallService(
288                mInCallServiceComponentNameY,
289                mInCallServiceFixtureY.getTestDouble());
290    }
291
292    /**
293     * Helper method for setting up the fake audio service.
294     * Calls to the fake audio service need to toggle the return
295     * value of AudioManager#isMicrophoneMute.
296     * @return mock of IAudioService
297     */
298    private IAudioService setupAudioService() {
299        IAudioService audioService = mock(IAudioService.class);
300        final AudioManager fakeAudioManager =
301                (AudioManager) mComponentContextFixture.getTestDouble()
302                        .getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
303
304        try {
305            doAnswer(new Answer() {
306                @Override
307                public Object answer(InvocationOnMock i) {
308                    Object[] args = i.getArguments();
309                    doReturn(args[0]).when(fakeAudioManager).isMicrophoneMute();
310                    return null;
311                }
312            }).when(audioService)
313                    .setMicrophoneMute(any(Boolean.class), any(String.class), any(Integer.class));
314
315        } catch (android.os.RemoteException e) {
316            // Do nothing, leave the faked microphone state as-is
317        }
318        return audioService;
319    }
320
321    private IdPair startOutgoingPhoneCall(
322            String number,
323            PhoneAccountHandle phoneAccountHandle,
324            ConnectionServiceFixture connectionServiceFixture) throws Exception {
325        reset(
326                connectionServiceFixture.getTestDouble(),
327                mInCallServiceFixtureX.getTestDouble(),
328                mInCallServiceFixtureY.getTestDouble());
329
330        assertEquals(
331                mInCallServiceFixtureX.mCallById.size(),
332                mInCallServiceFixtureY.mCallById.size());
333        assertEquals(
334                (mInCallServiceFixtureX.mInCallAdapter != null),
335                (mInCallServiceFixtureY.mInCallAdapter != null));
336
337        mNumOutgoingCallsMade++;
338        int startingNumConnections = connectionServiceFixture.mConnectionById.size();
339        int startingNumCalls = mInCallServiceFixtureX.mCallById.size();
340        boolean hasInCallAdapter = mInCallServiceFixtureX.mInCallAdapter != null;
341
342        Intent actionCallIntent = new Intent();
343        actionCallIntent.setData(Uri.parse("tel:" + number));
344        actionCallIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);
345        actionCallIntent.setAction(Intent.ACTION_CALL);
346        if (phoneAccountHandle != null) {
347            actionCallIntent.putExtra(
348                    TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
349                    phoneAccountHandle);
350        }
351
352        mTelecomSystem.getCallIntentProcessor().processIntent(actionCallIntent);
353
354        if (!hasInCallAdapter) {
355            verify(mInCallServiceFixtureX.getTestDouble())
356                    .setInCallAdapter(
357                            any(IInCallAdapter.class));
358            verify(mInCallServiceFixtureY.getTestDouble())
359                    .setInCallAdapter(
360                            any(IInCallAdapter.class));
361        }
362
363        ArgumentCaptor<Intent> newOutgoingCallIntent =
364                ArgumentCaptor.forClass(Intent.class);
365        ArgumentCaptor<BroadcastReceiver> newOutgoingCallReceiver =
366                ArgumentCaptor.forClass(BroadcastReceiver.class);
367
368        verify(mComponentContextFixture.getTestDouble().getApplicationContext(),
369                times(mNumOutgoingCallsMade))
370                .sendOrderedBroadcastAsUser(
371                        newOutgoingCallIntent.capture(),
372                        any(UserHandle.class),
373                        anyString(),
374                        anyInt(),
375                        newOutgoingCallReceiver.capture(),
376                        any(Handler.class),
377                        anyInt(),
378                        anyString(),
379                        any(Bundle.class));
380
381        // Pass on the new outgoing call Intent
382        // Set a dummy PendingResult so the BroadcastReceiver agrees to accept onReceive()
383        newOutgoingCallReceiver.getValue().setPendingResult(
384                new BroadcastReceiver.PendingResult(0, "", null, 0, true, false, null, 0, 0));
385        newOutgoingCallReceiver.getValue().setResultData(
386                newOutgoingCallIntent.getValue().getStringExtra(Intent.EXTRA_PHONE_NUMBER));
387        newOutgoingCallReceiver.getValue().onReceive(
388                mComponentContextFixture.getTestDouble(),
389                newOutgoingCallIntent.getValue());
390
391        assertEquals(startingNumConnections + 1, connectionServiceFixture.mConnectionById.size());
392
393        verify(connectionServiceFixture.getTestDouble()).createConnection(
394                eq(phoneAccountHandle),
395                anyString(),
396                any(ConnectionRequest.class),
397                anyBoolean(),
398                anyBoolean());
399
400        connectionServiceFixture.sendHandleCreateConnectionComplete(
401                connectionServiceFixture.mLatestConnectionId);
402
403        assertEquals(startingNumCalls + 1, mInCallServiceFixtureX.mCallById.size());
404        assertEquals(startingNumCalls + 1, mInCallServiceFixtureY.mCallById.size());
405
406        assertEquals(
407                mInCallServiceFixtureX.mLatestCallId,
408                mInCallServiceFixtureY.mLatestCallId);
409
410        return new IdPair(
411                connectionServiceFixture.mLatestConnectionId,
412                mInCallServiceFixtureX.mLatestCallId);
413    }
414
415    private IdPair startIncomingPhoneCall(
416            String number,
417            PhoneAccountHandle phoneAccountHandle,
418            final ConnectionServiceFixture connectionServiceFixture) throws Exception {
419        reset(
420                connectionServiceFixture.getTestDouble(),
421                mInCallServiceFixtureX.getTestDouble(),
422                mInCallServiceFixtureY.getTestDouble());
423
424        assertEquals(
425                mInCallServiceFixtureX.mCallById.size(),
426                mInCallServiceFixtureY.mCallById.size());
427        assertEquals(
428                (mInCallServiceFixtureX.mInCallAdapter != null),
429                (mInCallServiceFixtureY.mInCallAdapter != null));
430
431        final int startingNumConnections = connectionServiceFixture.mConnectionById.size();
432        final int startingNumCalls = mInCallServiceFixtureX.mCallById.size();
433        boolean hasInCallAdapter = mInCallServiceFixtureX.mInCallAdapter != null;
434
435        Bundle extras = new Bundle();
436        extras.putParcelable(
437                TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
438                Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null));
439        mTelecomSystem.getTelecomServiceImpl().getBinder()
440                .addNewIncomingCall(phoneAccountHandle, extras);
441
442        verify(connectionServiceFixture.getTestDouble()).createConnection(
443                any(PhoneAccountHandle.class),
444                anyString(),
445                any(ConnectionRequest.class),
446                eq(true),
447                eq(false));
448
449        connectionServiceFixture.sendHandleCreateConnectionComplete(
450                connectionServiceFixture.mLatestConnectionId);
451        connectionServiceFixture.sendSetRinging(
452                connectionServiceFixture.mLatestConnectionId);
453
454        // For the case of incoming calls, Telecom connecting the InCall services and adding the
455        // Call is triggered by the async completion of the CallerInfoAsyncQuery. Once the Call
456        // is added, future interactions as triggered by the ConnectionService, through the various
457        // test fixtures, will be synchronous.
458
459        if (!hasInCallAdapter) {
460            verify(
461                    mInCallServiceFixtureX.getTestDouble(),
462                    timeout(TEST_TIMEOUT))
463                    .setInCallAdapter(
464                            any(IInCallAdapter.class));
465            verify(
466                    mInCallServiceFixtureY.getTestDouble(),
467                    timeout(TEST_TIMEOUT))
468                    .setInCallAdapter(
469                            any(IInCallAdapter.class));
470        }
471
472        // Give the InCallService time to respond
473
474        assertTrueWithTimeout(new Predicate<Void>() {
475            @Override
476            public boolean apply(Void v) {
477                return mInCallServiceFixtureX.mInCallAdapter != null;
478            }
479        });
480
481        assertTrueWithTimeout(new Predicate<Void>() {
482            @Override
483            public boolean apply(Void v) {
484                return mInCallServiceFixtureY.mInCallAdapter != null;
485            }
486        });
487
488        verify(
489                mInCallServiceFixtureX.getTestDouble(),
490                timeout(TEST_TIMEOUT))
491                .addCall(
492                        any(ParcelableCall.class));
493        verify(
494                mInCallServiceFixtureY.getTestDouble(),
495                timeout(TEST_TIMEOUT))
496                .addCall(
497                        any(ParcelableCall.class));
498
499        // Give the InCallService time to respond
500
501        assertTrueWithTimeout(new Predicate<Void>() {
502            @Override
503            public boolean apply(Void v) {
504                return startingNumConnections + 1 ==
505                        connectionServiceFixture.mConnectionById.size();
506            }
507        });
508        assertTrueWithTimeout(new Predicate<Void>() {
509            @Override
510            public boolean apply(Void v) {
511                return startingNumCalls + 1 == mInCallServiceFixtureX.mCallById.size();
512            }
513        });
514        assertTrueWithTimeout(new Predicate<Void>() {
515            @Override
516            public boolean apply(Void v) {
517                return startingNumCalls + 1 == mInCallServiceFixtureY.mCallById.size();
518            }
519        });
520
521        assertEquals(
522                mInCallServiceFixtureX.mLatestCallId,
523                mInCallServiceFixtureY.mLatestCallId);
524
525        return new IdPair(
526                connectionServiceFixture.mLatestConnectionId,
527                mInCallServiceFixtureX.mLatestCallId);
528    }
529
530    private void rapidFire(Runnable... tasks) {
531        final CyclicBarrier barrier = new CyclicBarrier(tasks.length);
532        final CountDownLatch latch = new CountDownLatch(tasks.length);
533        for (int i = 0; i < tasks.length; i++) {
534            final Runnable task = tasks[i];
535            new Thread(new Runnable() {
536                @Override
537                public void run() {
538                    try {
539                        barrier.await();
540                        task.run();
541                    } catch (InterruptedException | BrokenBarrierException e){
542                        Log.e(TelecomSystemTest.this, e, "Unexpectedly interrupted");
543                    } finally {
544                        latch.countDown();
545                    }
546                }
547            }).start();
548        }
549        try {
550            latch.await();
551        } catch (InterruptedException e) {
552            Log.e(TelecomSystemTest.this, e, "Unexpectedly interrupted");
553        }
554    }
555
556    // A simple outgoing call, verifying that the appropriate connection service is contacted,
557    // the proper lifecycle is followed, and both In-Call Services are updated correctly.
558    private IdPair startAndMakeActiveOutgoingCall(
559            String number,
560            PhoneAccountHandle phoneAccountHandle,
561            ConnectionServiceFixture connectionServiceFixture) throws Exception {
562        IdPair ids = startOutgoingPhoneCall(number, phoneAccountHandle, connectionServiceFixture);
563
564        connectionServiceFixture.sendSetDialing(ids.mConnectionId);
565        assertEquals(Call.STATE_DIALING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
566        assertEquals(Call.STATE_DIALING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
567
568        connectionServiceFixture.sendSetActive(ids.mConnectionId);
569        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
570        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
571
572        return ids;
573    }
574
575    public void testBasicConferenceCall() throws Exception {
576        makeConferenceCall();
577    }
578
579    public void testAddCallToConference1() throws Exception {
580        ParcelableCall conferenceCall = makeConferenceCall();
581        IdPair callId3 = startAndMakeActiveOutgoingCall(
582                "650-555-1214",
583                mPhoneAccountA0.getAccountHandle(),
584                mConnectionServiceFixtureA);
585        // testAddCallToConference{1,2} differ in the order of arguments to InCallAdapter#conference
586        mInCallServiceFixtureX.getInCallAdapter().conference(
587                conferenceCall.getId(), callId3.mCallId);
588        Thread.sleep(200);
589
590        ParcelableCall call3 = mInCallServiceFixtureX.getCall(callId3.mCallId);
591        ParcelableCall updatedConference = mInCallServiceFixtureX.getCall(conferenceCall.getId());
592        assertEquals(conferenceCall.getId(), call3.getParentCallId());
593        assertEquals(3, updatedConference.getChildCallIds().size());
594        assertTrue(updatedConference.getChildCallIds().contains(callId3.mCallId));
595    }
596
597    public void testAddCallToConference2() throws Exception {
598        ParcelableCall conferenceCall = makeConferenceCall();
599        IdPair callId3 = startAndMakeActiveOutgoingCall(
600                "650-555-1214",
601                mPhoneAccountA0.getAccountHandle(),
602                mConnectionServiceFixtureA);
603        mInCallServiceFixtureX.getInCallAdapter().conference(
604                callId3.mCallId, conferenceCall.getId());
605        Thread.sleep(200);
606
607        ParcelableCall call3 = mInCallServiceFixtureX.getCall(callId3.mCallId);
608        ParcelableCall updatedConference = mInCallServiceFixtureX.getCall(conferenceCall.getId());
609        assertEquals(conferenceCall.getId(), call3.getParentCallId());
610        assertEquals(3, updatedConference.getChildCallIds().size());
611        assertTrue(updatedConference.getChildCallIds().contains(callId3.mCallId));
612    }
613
614    private ParcelableCall makeConferenceCall() throws Exception {
615        IdPair callId1 = startAndMakeActiveOutgoingCall(
616                "650-555-1212",
617                mPhoneAccountA0.getAccountHandle(),
618                mConnectionServiceFixtureA);
619
620        IdPair callId2 = startAndMakeActiveOutgoingCall(
621                "650-555-1213",
622                mPhoneAccountA0.getAccountHandle(),
623                mConnectionServiceFixtureA);
624
625        IInCallAdapter inCallAdapter = mInCallServiceFixtureX.getInCallAdapter();
626        inCallAdapter.conference(callId1.mCallId, callId2.mCallId);
627        // Wait for wacky non-deterministic behavior
628        Thread.sleep(200);
629        ParcelableCall call1 = mInCallServiceFixtureX.getCall(callId1.mCallId);
630        ParcelableCall call2 = mInCallServiceFixtureX.getCall(callId2.mCallId);
631        // Check that the two calls end up with a parent in the end
632        assertNotNull(call1.getParentCallId());
633        assertNotNull(call2.getParentCallId());
634        assertEquals(call1.getParentCallId(), call2.getParentCallId());
635
636        // Check to make sure that the parent call made it to the in-call service
637        String parentCallId = call1.getParentCallId();
638        ParcelableCall conferenceCall = mInCallServiceFixtureX.getCall(parentCallId);
639        assertEquals(2, conferenceCall.getChildCallIds().size());
640        assertTrue(conferenceCall.getChildCallIds().contains(callId1.mCallId));
641        assertTrue(conferenceCall.getChildCallIds().contains(callId2.mCallId));
642        return conferenceCall;
643    }
644
645    public void testSingleOutgoingCallLocalDisconnect() throws Exception {
646        IdPair ids = startAndMakeActiveOutgoingCall(
647                "650-555-1212",
648                mPhoneAccountA0.getAccountHandle(),
649                mConnectionServiceFixtureA);
650
651        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(ids.mCallId);
652        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
653        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
654
655        mConnectionServiceFixtureA.sendSetDisconnected(ids.mConnectionId, DisconnectCause.LOCAL);
656        assertEquals(Call.STATE_DISCONNECTED,
657                mInCallServiceFixtureX.getCall(ids.mCallId).getState());
658        assertEquals(Call.STATE_DISCONNECTED,
659                mInCallServiceFixtureY.getCall(ids.mCallId).getState());
660    }
661
662    public void testSingleOutgoingCallRemoteDisconnect() throws Exception {
663        IdPair ids = startAndMakeActiveOutgoingCall(
664                "650-555-1212",
665                mPhoneAccountA0.getAccountHandle(),
666                mConnectionServiceFixtureA);
667
668        mConnectionServiceFixtureA.sendSetDisconnected(ids.mConnectionId, DisconnectCause.LOCAL);
669        assertEquals(Call.STATE_DISCONNECTED,
670                mInCallServiceFixtureX.getCall(ids.mCallId).getState());
671        assertEquals(Call.STATE_DISCONNECTED,
672                mInCallServiceFixtureY.getCall(ids.mCallId).getState());
673    }
674
675    // A simple incoming call, similar in scope to the previous test
676    private IdPair startAndMakeActiveIncomingCall(
677            String number,
678            PhoneAccountHandle phoneAccountHandle,
679            ConnectionServiceFixture connectionServiceFixture) throws Exception {
680        IdPair ids = startIncomingPhoneCall(number, phoneAccountHandle, connectionServiceFixture);
681
682        assertEquals(Call.STATE_RINGING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
683        assertEquals(Call.STATE_RINGING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
684
685        mInCallServiceFixtureX.mInCallAdapter
686                .answerCall(ids.mCallId, VideoProfile.STATE_AUDIO_ONLY);
687
688        verify(connectionServiceFixture.getTestDouble())
689                .answer(ids.mConnectionId);
690
691        connectionServiceFixture.sendSetActive(ids.mConnectionId);
692        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
693        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
694
695        return ids;
696    }
697
698    public void testSingleIncomingCallLocalDisconnect() throws Exception {
699        IdPair ids = startAndMakeActiveIncomingCall(
700                "650-555-1212",
701                mPhoneAccountA0.getAccountHandle(),
702                mConnectionServiceFixtureA);
703
704        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(ids.mCallId);
705        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
706        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
707
708        mConnectionServiceFixtureA.sendSetDisconnected(ids.mConnectionId, DisconnectCause.LOCAL);
709        assertEquals(Call.STATE_DISCONNECTED,
710                mInCallServiceFixtureX.getCall(ids.mCallId).getState());
711        assertEquals(Call.STATE_DISCONNECTED,
712                mInCallServiceFixtureY.getCall(ids.mCallId).getState());
713    }
714
715    public void testSingleIncomingCallRemoteDisconnect() throws Exception {
716        IdPair ids = startAndMakeActiveIncomingCall(
717                "650-555-1212",
718                mPhoneAccountA0.getAccountHandle(),
719                mConnectionServiceFixtureA);
720
721        mConnectionServiceFixtureA.sendSetDisconnected(ids.mConnectionId, DisconnectCause.LOCAL);
722        assertEquals(Call.STATE_DISCONNECTED,
723                mInCallServiceFixtureX.getCall(ids.mCallId).getState());
724        assertEquals(Call.STATE_DISCONNECTED,
725                mInCallServiceFixtureY.getCall(ids.mCallId).getState());
726    }
727
728    public void do_testDeadlockOnOutgoingCall() throws Exception {
729        final IdPair ids = startOutgoingPhoneCall(
730                "650-555-1212",
731                mPhoneAccountA0.getAccountHandle(),
732                mConnectionServiceFixtureA);
733        rapidFire(
734                new Runnable() {
735                    @Override
736                    public void run() {
737                        while (mCallerInfoAsyncQueryFactoryFixture.mRequests.size() > 0) {
738                            mCallerInfoAsyncQueryFactoryFixture.mRequests.remove(0).reply();
739                        }
740                    }
741                },
742                new Runnable() {
743                    @Override
744                    public void run() {
745                        try {
746                            mConnectionServiceFixtureA.sendSetActive(ids.mConnectionId);
747                        } catch (Exception e) {
748                            Log.e(this, e, "");
749                        }
750                    }
751                });
752    }
753
754    public void testDeadlockOnOutgoingCall() throws Exception {
755        for (int i = 0; i < 100; i++) {
756            TelecomSystemTest test = new TelecomSystemTest();
757            test.setContext(getContext());
758            test.setTestContext(getTestContext());
759            test.setName(getName());
760            test.setUp();
761            test.do_testDeadlockOnOutgoingCall();
762            test.tearDown();
763        }
764    }
765
766    public void testIncomingThenOutgoingCalls() throws Exception {
767        // TODO: We have to use the same PhoneAccount for both; see http://b/18461539
768        IdPair incoming = startAndMakeActiveIncomingCall(
769                "650-555-2323",
770                mPhoneAccountA0.getAccountHandle(),
771                mConnectionServiceFixtureA);
772        IdPair outgoing = startAndMakeActiveOutgoingCall(
773                "650-555-1212",
774                mPhoneAccountA0.getAccountHandle(),
775                mConnectionServiceFixtureA);
776    }
777
778    public void testOutgoingThenIncomingCalls() throws Exception {
779        // TODO: We have to use the same PhoneAccount for both; see http://b/18461539
780        IdPair outgoing = startAndMakeActiveOutgoingCall(
781                "650-555-1212",
782                mPhoneAccountA0.getAccountHandle(),
783                mConnectionServiceFixtureA);
784        IdPair incoming = startAndMakeActiveIncomingCall(
785                "650-555-2323",
786                mPhoneAccountA0.getAccountHandle(),
787                mConnectionServiceFixtureA);
788        verify(mConnectionServiceFixtureA.getTestDouble())
789                .hold(outgoing.mConnectionId);
790        mConnectionServiceFixtureA.mConnectionById.get(outgoing.mConnectionId).state =
791                Connection.STATE_HOLDING;
792        mConnectionServiceFixtureA.sendSetOnHold(outgoing.mConnectionId);
793        assertEquals(
794                Call.STATE_HOLDING,
795                mInCallServiceFixtureX.getCall(outgoing.mCallId).getState());
796        assertEquals(
797                Call.STATE_HOLDING,
798                mInCallServiceFixtureY.getCall(outgoing.mCallId).getState());
799    }
800
801    public void testAudioManagerOperations() throws Exception {
802        AudioManager audioManager = (AudioManager) mComponentContextFixture.getTestDouble()
803                .getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
804
805        IdPair outgoing = startAndMakeActiveOutgoingCall(
806                "650-555-1212",
807                mPhoneAccountA0.getAccountHandle(),
808                mConnectionServiceFixtureA);
809
810        verify(audioManager, timeout(TEST_TIMEOUT))
811                .requestAudioFocusForCall(anyInt(), anyInt());
812        verify(audioManager, timeout(TEST_TIMEOUT).atLeastOnce())
813                .setMode(AudioManager.MODE_IN_CALL);
814
815        mInCallServiceFixtureX.mInCallAdapter.mute(true);
816        verify(mAudioService, timeout(TEST_TIMEOUT))
817                .setMicrophoneMute(eq(true), any(String.class), any(Integer.class));
818        mInCallServiceFixtureX.mInCallAdapter.mute(false);
819        verify(mAudioService, timeout(TEST_TIMEOUT))
820                .setMicrophoneMute(eq(false), any(String.class), any(Integer.class));
821
822        mInCallServiceFixtureX.mInCallAdapter.setAudioRoute(CallAudioState.ROUTE_SPEAKER);
823        verify(audioManager, timeout(TEST_TIMEOUT))
824                .setSpeakerphoneOn(true);
825        mInCallServiceFixtureX.mInCallAdapter.setAudioRoute(CallAudioState.ROUTE_EARPIECE);
826        verify(audioManager, timeout(TEST_TIMEOUT))
827                .setSpeakerphoneOn(false);
828
829        mConnectionServiceFixtureA.
830                sendSetDisconnected(outgoing.mConnectionId, DisconnectCause.REMOTE);
831
832        verify(audioManager, timeout(TEST_TIMEOUT))
833                .abandonAudioFocusForCall();
834        verify(audioManager, timeout(TEST_TIMEOUT).atLeastOnce())
835                .setMode(AudioManager.MODE_NORMAL);
836    }
837
838    protected static void assertTrueWithTimeout(Predicate<Void> predicate) {
839        int elapsed = 0;
840        while (elapsed < TEST_TIMEOUT) {
841            if (predicate.apply(null)) {
842                return;
843            } else {
844                try {
845                    Thread.sleep(TEST_POLL_INTERVAL);
846                    elapsed += TEST_POLL_INTERVAL;
847                } catch (InterruptedException e) {
848                    fail(e.toString());
849                }
850            }
851        }
852        fail("Timeout in assertTrueWithTimeout");
853    }
854}
855