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