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