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 
17 package com.android.server.telecom.tests;
18 
19 import com.android.internal.telecom.IConnectionService;
20 import com.android.internal.telecom.IConnectionServiceAdapter;
21 import com.android.internal.telecom.IVideoProvider;
22 import com.android.internal.telecom.RemoteServiceCallback;
23 import com.android.server.telecom.Log;
24 
25 import junit.framework.TestCase;
26 
27 import org.mockito.Mockito;
28 
29 import android.content.ComponentName;
30 import android.net.Uri;
31 import android.os.Bundle;
32 import android.os.IBinder;
33 import android.os.IInterface;
34 import android.os.RemoteException;
35 import android.telecom.CallAudioState;
36 import android.telecom.Conference;
37 import android.telecom.Connection;
38 import android.telecom.ConnectionRequest;
39 import android.telecom.ConnectionService;
40 import android.telecom.DisconnectCause;
41 import android.telecom.ParcelableConference;
42 import android.telecom.ParcelableConnection;
43 import android.telecom.PhoneAccountHandle;
44 import android.telecom.StatusHints;
45 import android.telecom.TelecomManager;
46 
47 import com.google.android.collect.Lists;
48 
49 import java.lang.Override;
50 import java.util.ArrayList;
51 import java.util.Collection;
52 import java.util.HashMap;
53 import java.util.HashSet;
54 import java.util.List;
55 import java.util.Map;
56 import java.util.Set;
57 import java.util.concurrent.CountDownLatch;
58 import 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  */
64 public 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
onCreateUnknownConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)79         public Connection onCreateUnknownConnection(
80                 PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
81             mLatestConnection = new FakeConnection(request.getVideoState(), request.getAddress());
82             return mLatestConnection;
83         }
84 
85         @Override
onCreateIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)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
onCreateOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)103         public Connection onCreateOutgoingConnection(
104                 PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
105             mLatestConnection = new FakeConnection(request.getVideoState(), request.getAddress());
106             return mLatestConnection;
107         }
108 
109         @Override
onConference(Connection cxn1, Connection cxn2)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 {
FakeConnection(int videoState, Uri address)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
onExtrasChanged(Bundle extras)136         public void onExtrasChanged(Bundle extras) {
137             mExtrasLock.countDown();
138         }
139     }
140 
141     public class FakeConference extends Conference {
FakeConference()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
onMerge(Connection connection)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
onExtrasChanged(Bundle extras)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
addConnectionServiceAdapter(IConnectionServiceAdapter adapter)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
removeConnectionServiceAdapter(IConnectionServiceAdapter adapter)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
createConnection(PhoneAccountHandle connectionManagerPhoneAccount, String id, ConnectionRequest request, boolean isIncoming, boolean isUnknown)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
abort(String callId)212         public void abort(String callId) throws RemoteException { }
213 
214         @Override
answerVideo(String callId, int videoState)215         public void answerVideo(String callId, int videoState) throws RemoteException { }
216 
217         @Override
answer(String callId)218         public void answer(String callId) throws RemoteException { }
219 
220         @Override
reject(String callId)221         public void reject(String callId) throws RemoteException {
222             rejectedCallIds.add(callId);
223         }
224 
225         @Override
rejectWithMessage(String callId, String message)226         public void rejectWithMessage(String callId, String message) throws RemoteException { }
227 
228         @Override
disconnect(String callId)229         public void disconnect(String callId) throws RemoteException { }
230 
231         @Override
silence(String callId)232         public void silence(String callId) throws RemoteException { }
233 
234         @Override
hold(String callId)235         public void hold(String callId) throws RemoteException { }
236 
237         @Override
unhold(String callId)238         public void unhold(String callId) throws RemoteException { }
239 
240         @Override
onCallAudioStateChanged(String activeCallId, CallAudioState audioState)241         public void onCallAudioStateChanged(String activeCallId, CallAudioState audioState)
242                 throws RemoteException { }
243 
244         @Override
playDtmfTone(String callId, char digit)245         public void playDtmfTone(String callId, char digit) throws RemoteException { }
246 
247         @Override
stopDtmfTone(String callId)248         public void stopDtmfTone(String callId) throws RemoteException { }
249 
250         @Override
conference(String conferenceCallId, String callId)251         public void conference(String conferenceCallId, String callId) throws RemoteException {
252             mConnectionServiceDelegateAdapter.conference(conferenceCallId, callId);
253         }
254 
255         @Override
splitFromConference(String callId)256         public void splitFromConference(String callId) throws RemoteException { }
257 
258         @Override
mergeConference(String conferenceCallId)259         public void mergeConference(String conferenceCallId) throws RemoteException { }
260 
261         @Override
swapConference(String conferenceCallId)262         public void swapConference(String conferenceCallId) throws RemoteException { }
263 
264         @Override
onPostDialContinue(String callId, boolean proceed)265         public void onPostDialContinue(String callId, boolean proceed) throws RemoteException { }
266 
267         @Override
pullExternalCall(String callId)268         public void pullExternalCall(String callId) throws RemoteException { }
269 
270         @Override
sendCallEvent(String callId, String event, Bundle extras)271         public void sendCallEvent(String callId, String event, Bundle extras) throws RemoteException
272         {}
273 
onExtrasChanged(String callId, Bundle extras)274         public void onExtrasChanged(String callId, Bundle extras) throws RemoteException {
275             mConnectionServiceDelegateAdapter.onExtrasChanged(callId, extras);
276         }
277 
278         @Override
asBinder()279         public IBinder asBinder() {
280             return this;
281         }
282 
283         @Override
queryLocalInterface(String descriptor)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 
ConnectionServiceFixture()344     public ConnectionServiceFixture() throws Exception { }
345 
346     @Override
getTestDouble()347     public IConnectionService getTestDouble() {
348         return mConnectionServiceSpy;
349     }
350 
sendHandleCreateConnectionComplete(String id)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 
sendSetActive(String id)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 
sendSetRinging(String id)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 
sendSetDialing(String id)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 
sendSetDisconnected(String id, int disconnectCause)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 
sendSetOnHold(String id)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 
sendSetRingbackRequested(String id)396     public void sendSetRingbackRequested(String id) throws Exception {
397         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
398             a.setRingbackRequested(id, mConnectionById.get(id).ringing);
399         }
400     }
401 
sendSetConnectionCapabilities(String id)402     public void sendSetConnectionCapabilities(String id) throws Exception {
403         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
404             a.setConnectionCapabilities(id, mConnectionById.get(id).capabilities);
405         }
406     }
407 
sendSetIsConferenced(String id)408     public void sendSetIsConferenced(String id) throws Exception {
409         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
410             a.setIsConferenced(id, mConnectionById.get(id).conferenceId);
411         }
412     }
413 
sendAddConferenceCall(String id)414     public void sendAddConferenceCall(String id) throws Exception {
415         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
416             a.addConferenceCall(id, parcelable(mConferenceById.get(id)));
417         }
418     }
419 
sendRemoveCall(String id)420     public void sendRemoveCall(String id) throws Exception {
421         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
422             a.removeCall(id);
423         }
424     }
425 
sendOnPostDialWait(String id, String remaining)426     public void sendOnPostDialWait(String id, String remaining) throws Exception {
427         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
428             a.onPostDialWait(id, remaining);
429         }
430     }
431 
sendOnPostDialChar(String id, char nextChar)432     public void sendOnPostDialChar(String id, char nextChar) throws Exception {
433         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
434             a.onPostDialChar(id, nextChar);
435         }
436     }
437 
sendQueryRemoteConnectionServices()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 
sendSetVideoProvider(String id)465     public void sendSetVideoProvider(String id) throws Exception {
466         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
467             a.setVideoProvider(id, mConnectionById.get(id).videoProvider);
468         }
469     }
470 
sendSetVideoState(String id)471     public void sendSetVideoState(String id) throws Exception {
472         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
473             a.setVideoState(id, mConnectionById.get(id).videoState);
474         }
475     }
476 
sendSetIsVoipAudioMode(String id)477     public void sendSetIsVoipAudioMode(String id) throws Exception {
478         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
479             a.setIsVoipAudioMode(id, mConnectionById.get(id).isVoipAudioMode);
480         }
481     }
482 
sendSetStatusHints(String id)483     public void sendSetStatusHints(String id) throws Exception {
484         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
485             a.setStatusHints(id, mConnectionById.get(id).statusHints);
486         }
487     }
488 
sendSetAddress(String id)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 
sendSetCallerDisplayName(String id)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 
sendSetConferenceableConnections(String id)507     public void sendSetConferenceableConnections(String id) throws Exception {
508         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
509             a.setConferenceableConnections(id, mConnectionById.get(id).conferenceableConnectionIds);
510         }
511     }
512 
sendAddExistingConnection(String id)513     public void sendAddExistingConnection(String id) throws Exception {
514         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
515             a.addExistingConnection(id, parcelable(mConnectionById.get(id)));
516         }
517     }
518 
sendConnectionEvent(String id, String event, Bundle extras)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      */
waitForExtras()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 
parcelable(ConferenceInfo c)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 
parcelable(ConnectionInfo c)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