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