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