TelecomSystemTest.java revision 92694519535c54f542b4ef3973e9c1934f2feeff
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
19import static org.mockito.Matchers.any;
20import static org.mockito.Matchers.anyBoolean;
21import static org.mockito.Matchers.anyInt;
22import static org.mockito.Matchers.anyString;
23import static org.mockito.Matchers.eq;
24import static org.mockito.Mockito.mock;
25import static org.mockito.Mockito.timeout;
26import static org.mockito.Mockito.verify;
27import static org.mockito.Mockito.when;
28
29import android.content.BroadcastReceiver;
30import android.content.ComponentName;
31import android.content.Context;
32import android.content.Intent;
33import android.net.Uri;
34import android.os.Bundle;
35import android.os.Handler;
36import android.os.UserHandle;
37import android.telecom.ConnectionRequest;
38import android.telecom.DisconnectCause;
39import android.telecom.ParcelableCall;
40import android.telecom.PhoneAccount;
41import android.telecom.PhoneAccountHandle;
42import android.telecom.TelecomManager;
43import android.telephony.TelephonyManager;
44
45import com.android.internal.telecom.IInCallAdapter;
46import com.android.internal.telephony.CallerInfo;
47import com.android.internal.telephony.CallerInfoAsyncQuery;
48import com.android.server.telecom.CallerInfoAsyncQueryFactory;
49import com.android.server.telecom.CallState;
50import com.android.server.telecom.CallsManager;
51import com.android.server.telecom.HeadsetMediaButton;
52import com.android.server.telecom.HeadsetMediaButtonFactory;
53import com.android.server.telecom.InCallWakeLockController;
54import com.android.server.telecom.InCallWakeLockControllerFactory;
55import com.android.server.telecom.Log;
56import com.android.server.telecom.MissedCallNotifier;
57import com.android.server.telecom.ProximitySensorManager;
58import com.android.server.telecom.ProximitySensorManagerFactory;
59import com.android.server.telecom.TelecomSystem;
60
61import org.mockito.ArgumentCaptor;
62import org.mockito.Mock;
63
64import java.util.concurrent.BrokenBarrierException;
65import java.util.concurrent.CountDownLatch;
66import java.util.concurrent.CyclicBarrier;
67
68public class TelecomSystemTest extends TelecomTestCase {
69
70    static final int TEST_TIMEOUT = 1000;  // milliseconds
71
72    @Mock MissedCallNotifier mMissedCallNotifier;
73    @Mock HeadsetMediaButton mHeadsetMediaButton;
74    @Mock ProximitySensorManager mProximitySensorManager;
75    @Mock InCallWakeLockController mInCallWakeLockController;
76
77    final ComponentName mInCallServiceComponentNameX =
78            new ComponentName(
79                    "incall-service-package-X",
80                    "incall-service-class-X");
81    final ComponentName mInCallServiceComponentNameY =
82            new ComponentName(
83                    "incall-service-package-Y",
84                    "incall-service-class-Y");
85
86    InCallServiceFixture mInCallServiceFixtureX;
87    InCallServiceFixture mInCallServiceFixtureY;
88
89    final ComponentName mConnectionServiceComponentNameA =
90            new ComponentName(
91                    "connection-service-package-A",
92                    "connection-service-class-A");
93    final ComponentName mConnectionServiceComponentNameB =
94            new ComponentName(
95                    "connection-service-package-B",
96                    "connection-service-class-B");
97
98    final PhoneAccount mPhoneAccountA0 =
99            PhoneAccount.builder(
100                    new PhoneAccountHandle(
101                            mConnectionServiceComponentNameA,
102                            "id A 0"),
103                    "Phone account service A ID 0")
104                    .addSupportedUriScheme("tel")
105                    .setCapabilities(
106                            PhoneAccount.CAPABILITY_CALL_PROVIDER |
107                            PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
108                    .build();
109    final PhoneAccount mPhoneAccountA1 =
110            PhoneAccount.builder(
111                    new PhoneAccountHandle(
112                            mConnectionServiceComponentNameA,
113                            "id A 1"),
114                    "Phone account service A ID 1")
115                    .addSupportedUriScheme("tel")
116                    .setCapabilities(
117                            PhoneAccount.CAPABILITY_CALL_PROVIDER |
118                            PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
119                    .build();
120    final PhoneAccount mPhoneAccountB0 =
121            PhoneAccount.builder(
122                    new PhoneAccountHandle(
123                            mConnectionServiceComponentNameA,
124                            "id B 0"),
125                    "Phone account service B ID 0")
126                    .addSupportedUriScheme("tel")
127                    .setCapabilities(
128                            PhoneAccount.CAPABILITY_CALL_PROVIDER |
129                            PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
130                    .build();
131
132    ConnectionServiceFixture mConnectionServiceFixtureA;
133    ConnectionServiceFixture mConnectionServiceFixtureB;
134
135    CallerInfoAsyncQueryFactoryFixture mCallerInfoAsyncQueryFactoryFixture;
136
137    TelecomSystem mTelecomSystem;
138
139    @Override
140    public void setUp() throws Exception {
141        super.setUp();
142
143        // First set up information about the In-Call services in the mock Context, since
144        // Telecom will search for these as soon as it is instantiated
145        setupInCallServices();
146
147        // Next, create the TelecomSystem, our system under test
148        setupTelecomSystem();
149
150        // Finally, register the ConnectionServices with the PhoneAccountRegistrar of the
151        // now-running TelecomSystem
152        setupConnectionServices();
153    }
154
155    @Override
156    public void tearDown() throws Exception {
157        mTelecomSystem = null;
158        super.tearDown();
159    }
160
161    private void setupTelecomSystem() throws Exception {
162        HeadsetMediaButtonFactory headsetMediaButtonFactory =
163                mock(HeadsetMediaButtonFactory.class);
164        ProximitySensorManagerFactory proximitySensorManagerFactory =
165                mock(ProximitySensorManagerFactory.class);
166        InCallWakeLockControllerFactory inCallWakeLockControllerFactory =
167                mock(InCallWakeLockControllerFactory.class);
168
169        mCallerInfoAsyncQueryFactoryFixture = new CallerInfoAsyncQueryFactoryFixture();
170
171        when(headsetMediaButtonFactory.create(
172                any(Context.class),
173                any(CallsManager.class)))
174                .thenReturn(mHeadsetMediaButton);
175        when(proximitySensorManagerFactory.create(
176                any(Context.class),
177                any(CallsManager.class)))
178                .thenReturn(mProximitySensorManager);
179        when(inCallWakeLockControllerFactory.create(
180                any(Context.class),
181                any(CallsManager.class)))
182                .thenReturn(mInCallWakeLockController);
183
184        mTelecomSystem = new TelecomSystem(
185                mComponentContextFixture.getTestDouble(),
186                mMissedCallNotifier,
187                mCallerInfoAsyncQueryFactoryFixture.getTestDouble(),
188                headsetMediaButtonFactory,
189                proximitySensorManagerFactory,
190                inCallWakeLockControllerFactory);
191
192        verify(headsetMediaButtonFactory).create(
193                eq(mComponentContextFixture.getTestDouble().getApplicationContext()),
194                any(CallsManager.class));
195        verify(proximitySensorManagerFactory).create(
196                eq(mComponentContextFixture.getTestDouble().getApplicationContext()),
197                any(CallsManager.class));
198        verify(inCallWakeLockControllerFactory).create(
199                eq(mComponentContextFixture.getTestDouble().getApplicationContext()),
200                any(CallsManager.class));
201    }
202
203    private void setupConnectionServices() throws Exception {
204        mConnectionServiceFixtureA = new ConnectionServiceFixture();
205        mConnectionServiceFixtureB = new ConnectionServiceFixture();
206
207        mComponentContextFixture.addConnectionService(
208                mConnectionServiceComponentNameA,
209                mConnectionServiceFixtureA.getTestDouble());
210        mComponentContextFixture.addConnectionService(
211                mConnectionServiceComponentNameB,
212                mConnectionServiceFixtureB.getTestDouble());
213
214        mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountA0);
215        mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountA1);
216        mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountB0);
217    }
218
219    private void setupInCallServices() throws Exception {
220        mComponentContextFixture.putResource(
221                com.android.server.telecom.R.string.ui_default_package,
222                mInCallServiceComponentNameX.getPackageName());
223        mComponentContextFixture.putResource(
224                com.android.server.telecom.R.string.incall_default_class,
225                mInCallServiceComponentNameX.getClassName());
226
227        mInCallServiceFixtureX = new InCallServiceFixture();
228        mInCallServiceFixtureY = new InCallServiceFixture();
229
230        mComponentContextFixture.addInCallService(
231                mInCallServiceComponentNameX,
232                mInCallServiceFixtureX.getTestDouble());
233        mComponentContextFixture.addInCallService(
234                mInCallServiceComponentNameY,
235                mInCallServiceFixtureY.getTestDouble());
236    }
237
238    private String startOutgoingPhoneCall(
239            String number,
240            PhoneAccountHandle phoneAccountHandle,
241            ConnectionServiceFixture connectionServiceFixture) throws Exception {
242        Intent actionCallIntent = new Intent();
243        actionCallIntent.setData(Uri.parse("tel:" + number));
244        actionCallIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);
245        actionCallIntent.setAction(Intent.ACTION_CALL);
246        if (phoneAccountHandle != null) {
247            actionCallIntent.putExtra(
248                    TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
249                    phoneAccountHandle);
250        }
251
252        mTelecomSystem.getCallIntentProcessor().processIntent(actionCallIntent);
253
254        ArgumentCaptor<Intent> newOutgoingCallIntent =
255                ArgumentCaptor.forClass(Intent.class);
256        ArgumentCaptor<BroadcastReceiver> newOutgoingCallReceiver =
257                ArgumentCaptor.forClass(BroadcastReceiver.class);
258
259        verify(mComponentContextFixture.getTestDouble().getApplicationContext())
260                .sendOrderedBroadcastAsUser(
261                        newOutgoingCallIntent.capture(),
262                        any(UserHandle.class),
263                        anyString(),
264                        newOutgoingCallReceiver.capture(),
265                        any(Handler.class),
266                        anyInt(),
267                        anyString(),
268                        any(Bundle.class));
269
270        assertNotNull(mInCallServiceFixtureX.mInCallAdapter);
271        assertNotNull(mInCallServiceFixtureY.mInCallAdapter);
272
273        // Pass on the new outgoing call Intent
274        // Set a dummy PendingResult so the BroadcastReceiver agrees to accept onReceive()
275        newOutgoingCallReceiver.getValue().setPendingResult(
276                new BroadcastReceiver.PendingResult(0, "", null, 0, true, false, null, 0, 0));
277        newOutgoingCallReceiver.getValue().setResultData(
278                newOutgoingCallIntent.getValue().getStringExtra(Intent.EXTRA_PHONE_NUMBER));
279        newOutgoingCallReceiver.getValue().onReceive(
280                mComponentContextFixture.getTestDouble(),
281                newOutgoingCallIntent.getValue());
282
283        verify(connectionServiceFixture.getTestDouble()).createConnection(
284                eq(phoneAccountHandle),
285                anyString(),
286                any(ConnectionRequest.class),
287                anyBoolean(),
288                anyBoolean());
289
290        String id = connectionServiceFixture.mLatestConnectionId;
291
292        connectionServiceFixture.sendHandleCreateConnectionComplete(id);
293
294        return id;
295    }
296
297    private String startIncomingPhoneCall(
298            String number,
299            PhoneAccountHandle phoneAccountHandle,
300            ConnectionServiceFixture connectionServiceFixture) throws Exception {
301        Bundle extras = new Bundle();
302        extras.putParcelable(
303                TelephonyManager.EXTRA_INCOMING_NUMBER,
304                Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null));
305        mTelecomSystem.getTelecomServiceImpl().getBinder()
306                .addNewIncomingCall(phoneAccountHandle, extras);
307
308        verify(connectionServiceFixture.getTestDouble()).createConnection(
309                any(PhoneAccountHandle.class),
310                anyString(),
311                any(ConnectionRequest.class),
312                eq(true),
313                eq(false));
314
315        String id = connectionServiceFixture.mLatestConnectionId;
316
317        connectionServiceFixture.sendHandleCreateConnectionComplete(id);
318        connectionServiceFixture.sendSetRinging(id);
319
320        // For the case of incoming calls, Telecom connecting the InCall services and adding the
321        // Call is triggered by the async completion of the CallerInfoAsyncQuery. Once the Call
322        // is added, future interactions as triggered by the ConnectionService, through the various
323        // test fixtures, will be synchronous.
324
325        verify(
326                mInCallServiceFixtureX.getTestDouble(),
327                timeout(TEST_TIMEOUT))
328                .setInCallAdapter(
329                        any(IInCallAdapter.class));
330        verify(
331                mInCallServiceFixtureY.getTestDouble(),
332                timeout(TEST_TIMEOUT))
333                .setInCallAdapter(
334                        any(IInCallAdapter.class));
335
336        assertNotNull(mInCallServiceFixtureX.mInCallAdapter);
337        assertNotNull(mInCallServiceFixtureY.mInCallAdapter);
338
339        verify(
340                mInCallServiceFixtureX.getTestDouble(),
341                timeout(TEST_TIMEOUT))
342                .addCall(
343                        any(ParcelableCall.class));
344        verify(
345                mInCallServiceFixtureY.getTestDouble(),
346                timeout(TEST_TIMEOUT))
347                .addCall(
348                        any(ParcelableCall.class));
349
350        return id;
351    }
352
353    private void rapidFire(Runnable... tasks) {
354        final CyclicBarrier barrier = new CyclicBarrier(tasks.length);
355        final CountDownLatch latch = new CountDownLatch(tasks.length);
356        for (int i = 0; i < tasks.length; i++) {
357            final Runnable task = tasks[i];
358            new Thread(new Runnable() {
359                @Override
360                public void run() {
361                    try {
362                        barrier.await();
363                        task.run();
364                    } catch (InterruptedException | BrokenBarrierException e){
365                        Log.e(TelecomSystemTest.this, e, "Unexpectedly interrupted");
366                    } finally {
367                        latch.countDown();
368                    }
369                }
370            }).start();
371        }
372        try {
373            latch.await();
374        } catch (InterruptedException e) {
375            Log.e(TelecomSystemTest.this, e, "Unexpectedly interrupted");
376        }
377    }
378
379    // A simple outgoing call, verifying that the appropriate connection service is contacted,
380    // the proper lifecycle is followed, and both In-Call Services are updated correctly.
381    public void testSingleOutgoingCall() throws Exception {
382        String connectionId = startOutgoingPhoneCall(
383                "650-555-1212",
384                mPhoneAccountA0.getAccountHandle(),
385                mConnectionServiceFixtureA);
386
387        assertEquals(1, mConnectionServiceFixtureA.mConnectionServiceAdapters.size());
388        assertEquals(1, mConnectionServiceFixtureA.mConnectionById.size());
389
390        mConnectionServiceFixtureA.sendSetDialing(connectionId);
391
392        assertEquals(1, mInCallServiceFixtureX.mCallById.size());
393        String callId = mInCallServiceFixtureX.mLatestCallId;
394
395        assertEquals(CallState.DIALING, mInCallServiceFixtureX.getCall(callId).getState());
396        assertEquals(CallState.DIALING, mInCallServiceFixtureY.getCall(callId).getState());
397
398        mConnectionServiceFixtureA.sendSetActive(connectionId);
399        assertEquals(CallState.ACTIVE, mInCallServiceFixtureX.getCall(callId).getState());
400        assertEquals(CallState.ACTIVE, mInCallServiceFixtureY.getCall(callId).getState());
401
402        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(callId);;
403        assertEquals(CallState.ACTIVE, mInCallServiceFixtureX.getCall(callId).getState());
404        assertEquals(CallState.ACTIVE, mInCallServiceFixtureY.getCall(callId).getState());
405
406        mConnectionServiceFixtureA.sendSetDisconnected(connectionId, DisconnectCause.LOCAL);
407        assertEquals(CallState.DISCONNECTED, mInCallServiceFixtureX.getCall(callId).getState());
408        assertEquals(CallState.DISCONNECTED, mInCallServiceFixtureY.getCall(callId).getState());
409    }
410
411    // A simple incoming call, similar in scope to the previous test
412    public void testSingleIncomingCall() throws Exception {
413        String connectionId = startIncomingPhoneCall(
414                "650-555-1212",
415                mPhoneAccountA0.getAccountHandle(),
416                mConnectionServiceFixtureA);
417
418        assertEquals(1, mConnectionServiceFixtureA.mConnectionServiceAdapters.size());
419        assertEquals(1, mConnectionServiceFixtureA.mConnectionById.size());
420
421        assertEquals(1, mInCallServiceFixtureX.mCallById.size());
422        String callId = mInCallServiceFixtureX.mLatestCallId;
423
424        assertEquals(CallState.RINGING, mInCallServiceFixtureX.getCall(callId).getState());
425        assertEquals(CallState.RINGING, mInCallServiceFixtureY.getCall(callId).getState());
426
427        mConnectionServiceFixtureA.sendSetActive(connectionId);
428        assertEquals(CallState.ACTIVE, mInCallServiceFixtureX.getCall(callId).getState());
429        assertEquals(CallState.ACTIVE, mInCallServiceFixtureY.getCall(callId).getState());
430
431        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(callId);;
432        assertEquals(CallState.ACTIVE, mInCallServiceFixtureX.getCall(callId).getState());
433        assertEquals(CallState.ACTIVE, mInCallServiceFixtureY.getCall(callId).getState());
434
435        mConnectionServiceFixtureA.sendSetDisconnected(connectionId, DisconnectCause.LOCAL);
436        assertEquals(CallState.DISCONNECTED, mInCallServiceFixtureX.getCall(callId).getState());
437        assertEquals(CallState.DISCONNECTED, mInCallServiceFixtureY.getCall(callId).getState());
438    }
439
440    public void testDeadlockOnOutgoingCall() throws Exception {
441        for (int i = 0; i < 100; i++) {
442            TelecomSystemTest test = new TelecomSystemTest();
443            test.setContext(getContext());
444            test.setTestContext(getTestContext());
445            test.setName(getName());
446            test.setUp();
447            test.do_testDeadlockOnOutgoingCall();
448            test.tearDown();
449        }
450    }
451
452    public void do_testDeadlockOnOutgoingCall() throws Exception {
453        final String connectionId = startOutgoingPhoneCall(
454                "650-555-1212",
455                mPhoneAccountA0.getAccountHandle(),
456                mConnectionServiceFixtureA);
457        rapidFire(
458                new Runnable() {
459                    @Override
460                    public void run() {
461                        while (mCallerInfoAsyncQueryFactoryFixture.mRequests.size() > 0) {
462                            mCallerInfoAsyncQueryFactoryFixture.mRequests.remove(0).reply();
463                        }
464                    }
465                },
466                new Runnable() {
467                    @Override
468                    public void run() {
469                        try {
470                            mConnectionServiceFixtureA.sendSetActive(connectionId);
471                        } catch (Exception e) {
472                            Log.e(this, e, "");
473                        }
474                    }
475                });
476    }
477}
478