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