ConnectionServiceFixture.java revision b8b5a6976b44f92494971672ca3c1e6a71974d54
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(PhoneAccountHandle connectionManagerPhoneAccount,
244                String callId, ConnectionRequest request, boolean isIncoming,
245                Session.Info sessionInfo) throws RemoteException {
246            Log.i(ConnectionServiceFixture.this, "createConnectionFailed --> " + callId);
247
248            if (mConnectionById.containsKey(callId)) {
249                throw new RuntimeException("Connection already exists: " + callId);
250            }
251
252            // TODO(3p-calls): Implement this.
253        }
254
255        @Override
256        public void abort(String callId, Session.Info info) throws RemoteException { }
257
258        @Override
259        public void answerVideo(String callId, int videoState,
260                Session.Info info) throws RemoteException { }
261
262        @Override
263        public void answer(String callId, Session.Info info) throws RemoteException { }
264
265        @Override
266        public void reject(String callId, Session.Info info) throws RemoteException {
267            rejectedCallIds.add(callId);
268        }
269
270        @Override
271        public void rejectWithMessage(String callId, String message,
272                Session.Info info) throws RemoteException { }
273
274        @Override
275        public void disconnect(String callId, Session.Info info) throws RemoteException { }
276
277        @Override
278        public void silence(String callId, Session.Info info) throws RemoteException { }
279
280        @Override
281        public void hold(String callId, Session.Info info) throws RemoteException { }
282
283        @Override
284        public void unhold(String callId, Session.Info info) throws RemoteException { }
285
286        @Override
287        public void onCallAudioStateChanged(String activeCallId, CallAudioState audioState,
288                Session.Info info)
289                throws RemoteException { }
290
291        @Override
292        public void playDtmfTone(String callId, char digit,
293                Session.Info info) throws RemoteException { }
294
295        @Override
296        public void stopDtmfTone(String callId, Session.Info info) throws RemoteException { }
297
298        @Override
299        public void conference(String conferenceCallId, String callId,
300                Session.Info info) throws RemoteException {
301            mConnectionServiceDelegateAdapter.conference(conferenceCallId, callId, info);
302        }
303
304        @Override
305        public void splitFromConference(String callId, Session.Info info) throws RemoteException { }
306
307        @Override
308        public void mergeConference(String conferenceCallId,
309                Session.Info info) throws RemoteException { }
310
311        @Override
312        public void swapConference(String conferenceCallId,
313                Session.Info info) throws RemoteException { }
314
315        @Override
316        public void onPostDialContinue(String callId, boolean proceed,
317                Session.Info info) throws RemoteException { }
318
319        @Override
320        public void pullExternalCall(String callId, Session.Info info) throws RemoteException { }
321
322        @Override
323        public void sendCallEvent(String callId, String event, Bundle extras,
324                Session.Info info) throws RemoteException
325        {}
326
327        public void onExtrasChanged(String callId, Bundle extras,
328                Session.Info info) throws RemoteException {
329            mConnectionServiceDelegateAdapter.onExtrasChanged(callId, extras, info);
330        }
331
332        @Override
333        public void startRtt(String callId, ParcelFileDescriptor fromInCall,
334                ParcelFileDescriptor toInCall, Session.Info sessionInfo) throws RemoteException {
335
336        }
337
338        @Override
339        public void stopRtt(String callId, Session.Info sessionInfo) throws RemoteException {
340
341        }
342
343        @Override
344        public void respondToRttUpgradeRequest(String callId, ParcelFileDescriptor fromInCall,
345                ParcelFileDescriptor toInCall, Session.Info sessionInfo) throws RemoteException {
346
347        }
348
349        @Override
350        public IBinder asBinder() {
351            return this;
352        }
353
354        @Override
355        public IInterface queryLocalInterface(String descriptor) {
356            return this;
357        }
358    }
359
360    FakeConnectionServiceDelegate mConnectionServiceDelegate =
361            new FakeConnectionServiceDelegate();
362    private IConnectionService mConnectionServiceDelegateAdapter =
363            IConnectionService.Stub.asInterface(mConnectionServiceDelegate.onBind(null));
364
365    FakeConnectionService mConnectionService = new FakeConnectionService();
366    private IConnectionService.Stub mConnectionServiceSpy = Mockito.spy(mConnectionService);
367
368    public class ConnectionInfo {
369        PhoneAccountHandle connectionManagerPhoneAccount;
370        String id;
371        boolean ringing;
372        ConnectionRequest request;
373        boolean isIncoming;
374        boolean isUnknown;
375        int state;
376        int addressPresentation;
377        int capabilities;
378        int properties;
379        int supportedAudioRoutes;
380        StatusHints statusHints;
381        DisconnectCause disconnectCause;
382        String conferenceId;
383        String callerDisplayName;
384        int callerDisplayNamePresentation;
385        final List<String> conferenceableConnectionIds = new ArrayList<>();
386        IVideoProvider videoProvider;
387        Connection.VideoProvider videoProviderImpl;
388        MockVideoProvider mockVideoProvider;
389        int videoState;
390        boolean isVoipAudioMode;
391        Bundle extras;
392        boolean isConferenceCreated;
393    }
394
395    public class ConferenceInfo {
396        PhoneAccountHandle phoneAccount;
397        int state;
398        int capabilities;
399        int properties;
400        final List<String> connectionIds = new ArrayList<>();
401        IVideoProvider videoProvider;
402        int videoState;
403        long connectTimeMillis;
404        StatusHints statusHints;
405        Bundle extras;
406    }
407
408    public String mLatestConnectionId;
409    public Connection mLatestConnection;
410    public Conference mLatestConference;
411    public final Set<IConnectionServiceAdapter> mConnectionServiceAdapters = new HashSet<>();
412    public final Map<String, ConnectionInfo> mConnectionById = new HashMap<>();
413    public final Map<String, ConferenceInfo> mConferenceById = new HashMap<>();
414    public final List<ComponentName> mRemoteConnectionServiceNames = new ArrayList<>();
415    public final List<IBinder> mRemoteConnectionServices = new ArrayList<>();
416
417    public ConnectionServiceFixture() throws Exception { }
418
419    @Override
420    public IConnectionService getTestDouble() {
421        return mConnectionServiceSpy;
422    }
423
424    public void sendHandleCreateConnectionComplete(String id) throws Exception {
425        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
426            a.handleCreateConnectionComplete(
427                    id,
428                    mConnectionById.get(id).request,
429                    parcelable(mConnectionById.get(id)), null /*Session.Info*/);
430        }
431    }
432
433    public void sendSetActive(String id) throws Exception {
434        mConnectionById.get(id).state = Connection.STATE_ACTIVE;
435        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
436            a.setActive(id, null /*Session.Info*/);
437        }
438    }
439
440    public void sendSetRinging(String id) throws Exception {
441        mConnectionById.get(id).state = Connection.STATE_RINGING;
442        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
443            a.setRinging(id, null /*Session.Info*/);
444        }
445    }
446
447    public void sendSetDialing(String id) throws Exception {
448        mConnectionById.get(id).state = Connection.STATE_DIALING;
449        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
450            a.setDialing(id, null /*Session.Info*/);
451        }
452    }
453
454    public void sendSetDisconnected(String id, int disconnectCause) throws Exception {
455        mConnectionById.get(id).state = Connection.STATE_DISCONNECTED;
456        mConnectionById.get(id).disconnectCause = new DisconnectCause(disconnectCause);
457        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
458            a.setDisconnected(id, mConnectionById.get(id).disconnectCause, null /*Session.Info*/);
459        }
460    }
461
462    public void sendSetOnHold(String id) throws Exception {
463        mConnectionById.get(id).state = Connection.STATE_HOLDING;
464        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
465            a.setOnHold(id, null /*Session.Info*/);
466        }
467    }
468
469    public void sendSetRingbackRequested(String id) throws Exception {
470        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
471            a.setRingbackRequested(id, mConnectionById.get(id).ringing, null /*Session.Info*/);
472        }
473    }
474
475    public void sendSetConnectionCapabilities(String id) throws Exception {
476        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
477            a.setConnectionCapabilities(id, mConnectionById.get(id).capabilities,
478                    null /*Session.Info*/);
479        }
480    }
481
482    public void sendSetConnectionProperties(String id) throws Exception {
483        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
484            a.setConnectionProperties(id, mConnectionById.get(id).properties, null /*Session.Info*/);
485        }
486    }
487    public void sendSetIsConferenced(String id) throws Exception {
488        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
489            a.setIsConferenced(id, mConnectionById.get(id).conferenceId, null /*Session.Info*/);
490        }
491    }
492
493    public void sendAddConferenceCall(String id) throws Exception {
494        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
495            a.addConferenceCall(id, parcelable(mConferenceById.get(id)), null /*Session.Info*/);
496        }
497    }
498
499    public void sendRemoveCall(String id) throws Exception {
500        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
501            a.removeCall(id, null /*Session.Info*/);
502        }
503    }
504
505    public void sendOnPostDialWait(String id, String remaining) throws Exception {
506        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
507            a.onPostDialWait(id, remaining, null /*Session.Info*/);
508        }
509    }
510
511    public void sendOnPostDialChar(String id, char nextChar) throws Exception {
512        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
513            a.onPostDialChar(id, nextChar, null /*Session.Info*/);
514        }
515    }
516
517    public void sendQueryRemoteConnectionServices() throws Exception {
518        mRemoteConnectionServices.clear();
519        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
520            a.queryRemoteConnectionServices(new RemoteServiceCallback.Stub() {
521                @Override
522                public void onError() throws RemoteException {
523                    throw new RuntimeException();
524                }
525
526                @Override
527                public void onResult(
528                        List<ComponentName> names,
529                        List<IBinder> services)
530                        throws RemoteException {
531                    TestCase.assertEquals(names.size(), services.size());
532                    mRemoteConnectionServiceNames.addAll(names);
533                    mRemoteConnectionServices.addAll(services);
534                }
535
536                @Override
537                public IBinder asBinder() {
538                    return this;
539                }
540            }, null /*Session.Info*/);
541        }
542    }
543
544    public void sendSetVideoProvider(String id) throws Exception {
545        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
546            a.setVideoProvider(id, mConnectionById.get(id).videoProvider, null /*Session.Info*/);
547        }
548    }
549
550    public void sendSetVideoState(String id) throws Exception {
551        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
552            a.setVideoState(id, mConnectionById.get(id).videoState, null /*Session.Info*/);
553        }
554    }
555
556    public void sendSetIsVoipAudioMode(String id) throws Exception {
557        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
558            a.setIsVoipAudioMode(id, mConnectionById.get(id).isVoipAudioMode,
559                    null /*Session.Info*/);
560        }
561    }
562
563    public void sendSetStatusHints(String id) throws Exception {
564        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
565            a.setStatusHints(id, mConnectionById.get(id).statusHints, null /*Session.Info*/);
566        }
567    }
568
569    public void sendSetAddress(String id) throws Exception {
570        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
571            a.setAddress(
572                    id,
573                    mConnectionById.get(id).request.getAddress(),
574                    mConnectionById.get(id).addressPresentation, null /*Session.Info*/);
575        }
576    }
577
578    public void sendSetCallerDisplayName(String id) throws Exception {
579        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
580            a.setCallerDisplayName(
581                    id,
582                    mConnectionById.get(id).callerDisplayName,
583                    mConnectionById.get(id).callerDisplayNamePresentation, null /*Session.Info*/);
584        }
585    }
586
587    public void sendSetConferenceableConnections(String id) throws Exception {
588        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
589            a.setConferenceableConnections(id, mConnectionById.get(id).conferenceableConnectionIds,
590                    null /*Session.Info*/);
591        }
592    }
593
594    public void sendAddExistingConnection(String id) throws Exception {
595        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
596            a.addExistingConnection(id, parcelable(mConnectionById.get(id)), null /*Session.Info*/);
597        }
598    }
599
600    public void sendConnectionEvent(String id, String event, Bundle extras) throws Exception {
601        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
602            a.onConnectionEvent(id, event, extras, null /*Session.Info*/);
603        }
604    }
605
606    public void sendSetConferenceMergeFailed(String id) throws Exception {
607        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
608            a.setConferenceMergeFailed(id, null /*Session.Info*/);
609        }
610    }
611
612    /**
613     * Waits until the {@link Connection#onExtrasChanged(Bundle)} API has been called on a
614     * {@link Connection} or {@link Conference}.
615     */
616    public void waitForExtras() {
617        try {
618            mExtrasLock.await(TelecomSystemTest.TEST_TIMEOUT, TimeUnit.MILLISECONDS);
619        } catch (InterruptedException ie) {
620        }
621        mExtrasLock = new CountDownLatch(1);
622    }
623
624    private ParcelableConference parcelable(ConferenceInfo c) {
625        return new ParcelableConference(
626                c.phoneAccount,
627                c.state,
628                c.capabilities,
629                c.properties,
630                c.connectionIds,
631                c.videoProvider,
632                c.videoState,
633                c.connectTimeMillis,
634                c.statusHints,
635                c.extras);
636    }
637
638    private ParcelableConnection parcelable(ConnectionInfo c) {
639        return new ParcelableConnection(
640                c.request.getAccountHandle(),
641                c.state,
642                c.capabilities,
643                c.properties,
644                c.supportedAudioRoutes,
645                c.request.getAddress(),
646                c.addressPresentation,
647                c.callerDisplayName,
648                c.callerDisplayNamePresentation,
649                c.videoProvider,
650                c.videoState,
651                false, /* ringback requested */
652                false, /* voip audio mode */
653                0, /* Connect Time for conf call on this connection */
654                c.statusHints,
655                c.disconnectCause,
656                c.conferenceableConnectionIds,
657                c.extras);
658    }
659}
660