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