Home | History | Annotate | Download | only in tests
      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
     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