ConnectionServiceFixture.java revision 84771f8f87f5f582bf848b621eed8c6cd99d41b0
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 com.android.internal.telecom.IConnectionService;
20import com.android.internal.telecom.IConnectionServiceAdapter;
21import com.android.internal.telecom.IVideoProvider;
22import com.android.internal.telecom.RemoteServiceCallback;
23import com.android.server.telecom.Log;
24
25import junit.framework.TestCase;
26
27import org.mockito.Mockito;
28
29import android.content.ComponentName;
30import android.net.Uri;
31import android.os.Bundle;
32import android.os.IBinder;
33import android.os.IInterface;
34import android.os.RemoteException;
35import android.telecom.CallAudioState;
36import android.telecom.Conference;
37import android.telecom.Connection;
38import android.telecom.ConnectionRequest;
39import android.telecom.ConnectionService;
40import android.telecom.DisconnectCause;
41import android.telecom.ParcelableConference;
42import android.telecom.ParcelableConnection;
43import android.telecom.PhoneAccountHandle;
44import android.telecom.StatusHints;
45import android.telecom.TelecomManager;
46
47import com.google.android.collect.Lists;
48
49import java.lang.Override;
50import java.util.ArrayList;
51import java.util.Collection;
52import java.util.HashMap;
53import java.util.HashSet;
54import java.util.List;
55import java.util.Map;
56import java.util.Set;
57import java.util.concurrent.CountDownLatch;
58import java.util.concurrent.TimeUnit;
59
60/**
61 * Controls a test {@link IConnectionService} as would be provided by a source of connectivity
62 * to the Telecom framework.
63 */
64public class ConnectionServiceFixture implements TestFixture<IConnectionService> {
65    static int INVALID_VIDEO_STATE = -1;
66    public CountDownLatch mExtrasLock = new CountDownLatch(1);
67    static int NOT_SPECIFIED = 0;
68
69    /**
70     * Implementation of ConnectionService that performs no-ops for tasks normally meant for
71     * Telephony and reports success back to Telecom
72     */
73    public class FakeConnectionServiceDelegate extends ConnectionService {
74        int mVideoState = INVALID_VIDEO_STATE;
75        int mCapabilities = NOT_SPECIFIED;
76        int mProperties = NOT_SPECIFIED;
77
78        @Override
79        public Connection onCreateUnknownConnection(
80                PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
81            mLatestConnection = new FakeConnection(request.getVideoState(), request.getAddress());
82            return mLatestConnection;
83        }
84
85        @Override
86        public Connection onCreateIncomingConnection(
87                PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
88            FakeConnection fakeConnection =  new FakeConnection(
89                    mVideoState == INVALID_VIDEO_STATE ? request.getVideoState() : mVideoState,
90                    request.getAddress());
91            mLatestConnection = fakeConnection;
92            if (mCapabilities != NOT_SPECIFIED) {
93                fakeConnection.setConnectionCapabilities(mCapabilities);
94            }
95            if (mProperties != NOT_SPECIFIED) {
96                fakeConnection.setConnectionProperties(mProperties);
97            }
98
99            return fakeConnection;
100        }
101
102        @Override
103        public Connection onCreateOutgoingConnection(
104                PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
105            FakeConnection fakeConnection = new FakeConnection(request.getVideoState(),
106                    request.getAddress());
107            mLatestConnection = fakeConnection;
108            if (mCapabilities != NOT_SPECIFIED) {
109                fakeConnection.setConnectionCapabilities(mCapabilities);
110            }
111            if (mProperties != NOT_SPECIFIED) {
112                fakeConnection.setConnectionProperties(mProperties);
113            }
114            return fakeConnection;
115        }
116
117        @Override
118        public void onConference(Connection cxn1, Connection cxn2) {
119            // Usually, this is implemented by something in Telephony, which does a bunch of radio
120            // work to conference the two connections together. Here we just short-cut that and
121            // declare them conferenced.
122            Conference fakeConference = new FakeConference();
123            fakeConference.addConnection(cxn1);
124            fakeConference.addConnection(cxn2);
125            mLatestConference = fakeConference;
126            addConference(fakeConference);
127        }
128    }
129
130    public class FakeConnection extends Connection {
131        public FakeConnection(int videoState, Uri address) {
132            super();
133            int capabilities = getConnectionCapabilities();
134            capabilities |= CAPABILITY_MUTE;
135            capabilities |= CAPABILITY_SUPPORT_HOLD;
136            capabilities |= CAPABILITY_HOLD;
137            setVideoState(videoState);
138            setConnectionCapabilities(capabilities);
139            setActive();
140            setAddress(address, TelecomManager.PRESENTATION_ALLOWED);
141        }
142
143        @Override
144        public void onExtrasChanged(Bundle extras) {
145            mExtrasLock.countDown();
146        }
147    }
148
149    public class FakeConference extends Conference {
150        public FakeConference() {
151            super(null);
152            setConnectionCapabilities(
153                    Connection.CAPABILITY_SUPPORT_HOLD
154                            | Connection.CAPABILITY_HOLD
155                            | Connection.CAPABILITY_MUTE
156                            | Connection.CAPABILITY_MANAGE_CONFERENCE);
157        }
158
159        @Override
160        public void onMerge(Connection connection) {
161            // Do nothing besides inform the connection that it was merged into this conference.
162            connection.setConference(this);
163        }
164
165        @Override
166        public void onExtrasChanged(Bundle extras) {
167            Log.w(this, "FakeConference onExtrasChanged");
168            mExtrasLock.countDown();
169        }
170    }
171
172    public class FakeConnectionService extends IConnectionService.Stub {
173        List<String> rejectedCallIds = Lists.newArrayList();
174
175        @Override
176        public void addConnectionServiceAdapter(IConnectionServiceAdapter adapter)
177                throws RemoteException {
178            if (!mConnectionServiceAdapters.add(adapter)) {
179                throw new RuntimeException("Adapter already added: " + adapter);
180            }
181            mConnectionServiceDelegateAdapter.addConnectionServiceAdapter(adapter);
182        }
183
184        @Override
185        public void removeConnectionServiceAdapter(IConnectionServiceAdapter adapter)
186                throws RemoteException {
187            if (!mConnectionServiceAdapters.remove(adapter)) {
188                throw new RuntimeException("Adapter never added: " + adapter);
189            }
190            mConnectionServiceDelegateAdapter.removeConnectionServiceAdapter(adapter);
191        }
192
193        @Override
194        public void createConnection(PhoneAccountHandle connectionManagerPhoneAccount,
195                String id,
196                ConnectionRequest request, boolean isIncoming, boolean isUnknown)
197                throws RemoteException {
198            Log.i(ConnectionServiceFixture.this, "xoxox createConnection --> " + id);
199
200            if (mConnectionById.containsKey(id)) {
201                throw new RuntimeException("Connection already exists: " + id);
202            }
203            mLatestConnectionId = id;
204            ConnectionInfo c = new ConnectionInfo();
205            c.connectionManagerPhoneAccount = connectionManagerPhoneAccount;
206            c.id = id;
207            c.request = request;
208            c.isIncoming = isIncoming;
209            c.isUnknown = isUnknown;
210            c.capabilities |= Connection.CAPABILITY_HOLD | Connection.CAPABILITY_SUPPORT_HOLD;
211            c.videoState = request.getVideoState();
212            c.mockVideoProvider = new MockVideoProvider();
213            c.videoProvider = c.mockVideoProvider.getInterface();
214            mConnectionById.put(id, c);
215            mConnectionServiceDelegateAdapter.createConnection(connectionManagerPhoneAccount,
216                    id, request, isIncoming, isUnknown);
217        }
218
219        @Override
220        public void abort(String callId) throws RemoteException { }
221
222        @Override
223        public void answerVideo(String callId, int videoState) throws RemoteException { }
224
225        @Override
226        public void answer(String callId) throws RemoteException { }
227
228        @Override
229        public void reject(String callId) throws RemoteException {
230            rejectedCallIds.add(callId);
231        }
232
233        @Override
234        public void rejectWithMessage(String callId, String message) throws RemoteException { }
235
236        @Override
237        public void disconnect(String callId) throws RemoteException { }
238
239        @Override
240        public void silence(String callId) throws RemoteException { }
241
242        @Override
243        public void hold(String callId) throws RemoteException { }
244
245        @Override
246        public void unhold(String callId) throws RemoteException { }
247
248        @Override
249        public void onCallAudioStateChanged(String activeCallId, CallAudioState audioState)
250                throws RemoteException { }
251
252        @Override
253        public void playDtmfTone(String callId, char digit) throws RemoteException { }
254
255        @Override
256        public void stopDtmfTone(String callId) throws RemoteException { }
257
258        @Override
259        public void conference(String conferenceCallId, String callId) throws RemoteException {
260            mConnectionServiceDelegateAdapter.conference(conferenceCallId, callId);
261        }
262
263        @Override
264        public void splitFromConference(String callId) throws RemoteException { }
265
266        @Override
267        public void mergeConference(String conferenceCallId) throws RemoteException { }
268
269        @Override
270        public void swapConference(String conferenceCallId) throws RemoteException { }
271
272        @Override
273        public void onPostDialContinue(String callId, boolean proceed) throws RemoteException { }
274
275        @Override
276        public void pullExternalCall(String callId) throws RemoteException { }
277
278        @Override
279        public void sendCallEvent(String callId, String event, Bundle extras) throws RemoteException
280        {}
281
282        public void onExtrasChanged(String callId, Bundle extras) throws RemoteException {
283            mConnectionServiceDelegateAdapter.onExtrasChanged(callId, extras);
284        }
285
286        @Override
287        public IBinder asBinder() {
288            return this;
289        }
290
291        @Override
292        public IInterface queryLocalInterface(String descriptor) {
293            return this;
294        }
295    }
296
297    FakeConnectionServiceDelegate mConnectionServiceDelegate =
298            new FakeConnectionServiceDelegate();
299    private IConnectionService mConnectionServiceDelegateAdapter =
300            IConnectionService.Stub.asInterface(mConnectionServiceDelegate.onBind(null));
301
302    FakeConnectionService mConnectionService = new FakeConnectionService();
303    private IConnectionService.Stub mConnectionServiceSpy = Mockito.spy(mConnectionService);
304
305    public class ConnectionInfo {
306        PhoneAccountHandle connectionManagerPhoneAccount;
307        String id;
308        boolean ringing;
309        ConnectionRequest request;
310        boolean isIncoming;
311        boolean isUnknown;
312        int state;
313        int addressPresentation;
314        int capabilities;
315        int properties;
316        StatusHints statusHints;
317        DisconnectCause disconnectCause;
318        String conferenceId;
319        String callerDisplayName;
320        int callerDisplayNamePresentation;
321        final List<String> conferenceableConnectionIds = new ArrayList<>();
322        IVideoProvider videoProvider;
323        Connection.VideoProvider videoProviderImpl;
324        MockVideoProvider mockVideoProvider;
325        int videoState;
326        boolean isVoipAudioMode;
327        Bundle extras;
328    }
329
330    public class ConferenceInfo {
331        PhoneAccountHandle phoneAccount;
332        int state;
333        int capabilities;
334        int properties;
335        final List<String> connectionIds = new ArrayList<>();
336        IVideoProvider videoProvider;
337        int videoState;
338        long connectTimeMillis;
339        StatusHints statusHints;
340        Bundle extras;
341    }
342
343    public String mLatestConnectionId;
344    public Connection mLatestConnection;
345    public Conference mLatestConference;
346    public final Set<IConnectionServiceAdapter> mConnectionServiceAdapters = new HashSet<>();
347    public final Map<String, ConnectionInfo> mConnectionById = new HashMap<>();
348    public final Map<String, ConferenceInfo> mConferenceById = new HashMap<>();
349    public final List<ComponentName> mRemoteConnectionServiceNames = new ArrayList<>();
350    public final List<IBinder> mRemoteConnectionServices = new ArrayList<>();
351
352    public ConnectionServiceFixture() throws Exception { }
353
354    @Override
355    public IConnectionService getTestDouble() {
356        return mConnectionServiceSpy;
357    }
358
359    public void sendHandleCreateConnectionComplete(String id) throws Exception {
360        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
361            a.handleCreateConnectionComplete(
362                    id,
363                    mConnectionById.get(id).request,
364                    parcelable(mConnectionById.get(id)));
365        }
366    }
367
368    public void sendSetActive(String id) throws Exception {
369        mConnectionById.get(id).state = Connection.STATE_ACTIVE;
370        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
371            a.setActive(id);
372        }
373    }
374
375    public void sendSetRinging(String id) throws Exception {
376        mConnectionById.get(id).state = Connection.STATE_RINGING;
377        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
378            a.setRinging(id);
379        }
380    }
381
382    public void sendSetDialing(String id) throws Exception {
383        mConnectionById.get(id).state = Connection.STATE_DIALING;
384        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
385            a.setDialing(id);
386        }
387    }
388
389    public void sendSetDisconnected(String id, int disconnectCause) throws Exception {
390        mConnectionById.get(id).state = Connection.STATE_DISCONNECTED;
391        mConnectionById.get(id).disconnectCause = new DisconnectCause(disconnectCause);
392        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
393            a.setDisconnected(id, mConnectionById.get(id).disconnectCause);
394        }
395    }
396
397    public void sendSetOnHold(String id) throws Exception {
398        mConnectionById.get(id).state = Connection.STATE_HOLDING;
399        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
400            a.setOnHold(id);
401        }
402    }
403
404    public void sendSetRingbackRequested(String id) throws Exception {
405        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
406            a.setRingbackRequested(id, mConnectionById.get(id).ringing);
407        }
408    }
409
410    public void sendSetConnectionCapabilities(String id) throws Exception {
411        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
412            a.setConnectionCapabilities(id, mConnectionById.get(id).capabilities);
413        }
414    }
415
416    public void sendSetIsConferenced(String id) throws Exception {
417        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
418            a.setIsConferenced(id, mConnectionById.get(id).conferenceId);
419        }
420    }
421
422    public void sendAddConferenceCall(String id) throws Exception {
423        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
424            a.addConferenceCall(id, parcelable(mConferenceById.get(id)));
425        }
426    }
427
428    public void sendRemoveCall(String id) throws Exception {
429        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
430            a.removeCall(id);
431        }
432    }
433
434    public void sendOnPostDialWait(String id, String remaining) throws Exception {
435        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
436            a.onPostDialWait(id, remaining);
437        }
438    }
439
440    public void sendOnPostDialChar(String id, char nextChar) throws Exception {
441        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
442            a.onPostDialChar(id, nextChar);
443        }
444    }
445
446    public void sendQueryRemoteConnectionServices() throws Exception {
447        mRemoteConnectionServices.clear();
448        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
449            a.queryRemoteConnectionServices(new RemoteServiceCallback.Stub() {
450                @Override
451                public void onError() throws RemoteException {
452                    throw new RuntimeException();
453                }
454
455                @Override
456                public void onResult(
457                        List<ComponentName> names,
458                        List<IBinder> services)
459                        throws RemoteException {
460                    TestCase.assertEquals(names.size(), services.size());
461                    mRemoteConnectionServiceNames.addAll(names);
462                    mRemoteConnectionServices.addAll(services);
463                }
464
465                @Override
466                public IBinder asBinder() {
467                    return this;
468                }
469            });
470        }
471    }
472
473    public void sendSetVideoProvider(String id) throws Exception {
474        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
475            a.setVideoProvider(id, mConnectionById.get(id).videoProvider);
476        }
477    }
478
479    public void sendSetVideoState(String id) throws Exception {
480        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
481            a.setVideoState(id, mConnectionById.get(id).videoState);
482        }
483    }
484
485    public void sendSetIsVoipAudioMode(String id) throws Exception {
486        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
487            a.setIsVoipAudioMode(id, mConnectionById.get(id).isVoipAudioMode);
488        }
489    }
490
491    public void sendSetStatusHints(String id) throws Exception {
492        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
493            a.setStatusHints(id, mConnectionById.get(id).statusHints);
494        }
495    }
496
497    public void sendSetAddress(String id) throws Exception {
498        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
499            a.setAddress(
500                    id,
501                    mConnectionById.get(id).request.getAddress(),
502                    mConnectionById.get(id).addressPresentation);
503        }
504    }
505
506    public void sendSetCallerDisplayName(String id) throws Exception {
507        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
508            a.setCallerDisplayName(
509                    id,
510                    mConnectionById.get(id).callerDisplayName,
511                    mConnectionById.get(id).callerDisplayNamePresentation);
512        }
513    }
514
515    public void sendSetConferenceableConnections(String id) throws Exception {
516        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
517            a.setConferenceableConnections(id, mConnectionById.get(id).conferenceableConnectionIds);
518        }
519    }
520
521    public void sendAddExistingConnection(String id) throws Exception {
522        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
523            a.addExistingConnection(id, parcelable(mConnectionById.get(id)));
524        }
525    }
526
527    public void sendConnectionEvent(String id, String event, Bundle extras) throws Exception {
528        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
529            a.onConnectionEvent(id, event, extras);
530        }
531    }
532
533    /**
534     * Waits until the {@link Connection#onExtrasChanged(Bundle)} API has been called on a
535     * {@link Connection} or {@link Conference}.
536     */
537    public void waitForExtras() {
538        try {
539            mExtrasLock.await(TelecomSystemTest.TEST_TIMEOUT, TimeUnit.MILLISECONDS);
540        } catch (InterruptedException ie) {
541        }
542        mExtrasLock = new CountDownLatch(1);
543    }
544
545    private ParcelableConference parcelable(ConferenceInfo c) {
546        return new ParcelableConference(
547                c.phoneAccount,
548                c.state,
549                c.capabilities,
550                c.properties,
551                c.connectionIds,
552                c.videoProvider,
553                c.videoState,
554                c.connectTimeMillis,
555                c.statusHints,
556                c.extras);
557    }
558
559    private ParcelableConnection parcelable(ConnectionInfo c) {
560        return new ParcelableConnection(
561                c.request.getAccountHandle(),
562                c.state,
563                c.capabilities,
564                c.properties,
565                c.request.getAddress(),
566                c.addressPresentation,
567                c.callerDisplayName,
568                c.callerDisplayNamePresentation,
569                c.videoProvider,
570                c.videoState,
571                false, /* ringback requested */
572                false, /* voip audio mode */
573                0, /* Connect Time for conf call on this connection */
574                c.statusHints,
575                c.disconnectCause,
576                c.conferenceableConnectionIds,
577                c.extras);
578    }
579}
580