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