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