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