Home | History | Annotate | Download | only in cts
      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 android.telecom.cts;
     18 
     19 import static android.telecom.cts.TestUtils.PACKAGE;
     20 import static android.telecom.cts.TestUtils.TAG;
     21 import static android.telecom.cts.TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS;
     22 
     23 import static org.hamcrest.CoreMatchers.equalTo;
     24 import static org.hamcrest.CoreMatchers.not;
     25 import static org.junit.Assert.assertThat;
     26 
     27 import android.content.Context;
     28 import android.content.Intent;
     29 import android.net.Uri;
     30 import android.os.Bundle;
     31 import android.os.Handler;
     32 import android.os.HandlerThread;
     33 import android.telecom.Call;
     34 import android.telecom.CallAudioState;
     35 import android.telecom.Conference;
     36 import android.telecom.Connection;
     37 import android.telecom.InCallService;
     38 import android.telecom.PhoneAccount;
     39 import android.telecom.PhoneAccountHandle;
     40 import android.telecom.TelecomManager;
     41 import android.telecom.VideoProfile;
     42 import android.telecom.cts.MockInCallService.InCallServiceCallbacks;
     43 import android.telephony.PhoneStateListener;
     44 import android.telephony.TelephonyManager;
     45 import android.test.InstrumentationTestCase;
     46 import android.text.TextUtils;
     47 import android.util.Log;
     48 import android.util.Pair;
     49 
     50 import java.util.ArrayList;
     51 import java.util.List;
     52 import java.util.Objects;
     53 import java.util.concurrent.CountDownLatch;
     54 import java.util.concurrent.Semaphore;
     55 import java.util.concurrent.TimeUnit;
     56 
     57 /**
     58  * Base class for Telecom CTS tests that require a {@link CtsConnectionService} and
     59  * {@link MockInCallService} to verify Telecom functionality.
     60  */
     61 public class BaseTelecomTestWithMockServices extends InstrumentationTestCase {
     62 
     63     public static final int FLAG_REGISTER = 0x1;
     64     public static final int FLAG_ENABLE = 0x2;
     65 
     66     private static int sCounter = 5549999;
     67 
     68     Context mContext;
     69     TelecomManager mTelecomManager;
     70     TelephonyManager mTelephonyManager;
     71 
     72     TestUtils.InvokeCounter mOnBringToForegroundCounter;
     73     TestUtils.InvokeCounter mOnCallAudioStateChangedCounter;
     74     TestUtils.InvokeCounter mOnPostDialWaitCounter;
     75     TestUtils.InvokeCounter mOnCannedTextResponsesLoadedCounter;
     76     TestUtils.InvokeCounter mOnSilenceRingerCounter;
     77     TestUtils.InvokeCounter mOnConnectionEventCounter;
     78     TestUtils.InvokeCounter mOnExtrasChangedCounter;
     79     TestUtils.InvokeCounter mOnPropertiesChangedCounter;
     80     TestUtils.InvokeCounter mOnRttModeChangedCounter;
     81     TestUtils.InvokeCounter mOnRttStatusChangedCounter;
     82     TestUtils.InvokeCounter mOnRttInitiationFailedCounter;
     83     TestUtils.InvokeCounter mOnRttRequestCounter;
     84     TestUtils.InvokeCounter mOnHandoverCompleteCounter;
     85     TestUtils.InvokeCounter mOnHandoverFailedCounter;
     86     Bundle mPreviousExtras;
     87     int mPreviousProperties = -1;
     88 
     89     InCallServiceCallbacks mInCallCallbacks;
     90     String mPreviousDefaultDialer = null;
     91     MockConnectionService connectionService = null;
     92 
     93     HandlerThread mPhoneStateListenerThread;
     94     Handler mPhoneStateListenerHandler;
     95     TestPhoneStateListener mPhoneStateListener;
     96 
     97     static class TestPhoneStateListener extends PhoneStateListener {
     98         /** Semaphore released for every callback invocation. */
     99         public Semaphore mCallbackSemaphore = new Semaphore(0);
    100 
    101         List<Pair<Integer, String>> mCallStates = new ArrayList<>();
    102 
    103         @Override
    104         public void onCallStateChanged(int state, String number) {
    105             Log.i(TAG, "onCallStateChanged: state=" + state + ", number=" + number);
    106             mCallStates.add(Pair.create(state, number));
    107             mCallbackSemaphore.release();
    108         }
    109     }
    110 
    111     boolean mShouldTestTelecom = true;
    112 
    113     @Override
    114     protected void setUp() throws Exception {
    115         super.setUp();
    116         mContext = getInstrumentation().getContext();
    117 
    118         mShouldTestTelecom = TestUtils.shouldTestTelecom(mContext);
    119         if (!mShouldTestTelecom) {
    120             return;
    121         }
    122 
    123         mTelecomManager = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
    124         mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
    125 
    126         mPreviousDefaultDialer = TestUtils.getDefaultDialer(getInstrumentation());
    127         TestUtils.setDefaultDialer(getInstrumentation(), PACKAGE);
    128         setupCallbacks();
    129 
    130         // PhoneStateListener's public API registers the listener on the calling thread, which must
    131         // be a looper thread. So we need to create and register the listener in a custom looper
    132         // thread.
    133         mPhoneStateListenerThread = new HandlerThread("PhoneStateListenerThread");
    134         mPhoneStateListenerThread.start();
    135         mPhoneStateListenerHandler = new Handler(mPhoneStateListenerThread.getLooper());
    136         final CountDownLatch registeredLatch = new CountDownLatch(1);
    137         mPhoneStateListenerHandler.post(new Runnable() {
    138             @Override
    139             public void run() {
    140                 mPhoneStateListener = new TestPhoneStateListener();
    141                 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
    142                 registeredLatch.countDown();
    143             }
    144         });
    145         registeredLatch.await(
    146                 TestUtils.WAIT_FOR_PHONE_STATE_LISTENER_REGISTERED_TIMEOUT_S, TimeUnit.SECONDS);
    147     }
    148 
    149     @Override
    150     protected void tearDown() throws Exception {
    151         super.tearDown();
    152         if (!mShouldTestTelecom) {
    153             return;
    154         }
    155 
    156         final CountDownLatch unregisteredLatch = new CountDownLatch(1);
    157         mPhoneStateListenerHandler.post(new Runnable() {
    158             @Override
    159             public void run() {
    160                 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
    161                 unregisteredLatch.countDown();
    162             }
    163         });
    164         unregisteredLatch.await(
    165                 TestUtils.WAIT_FOR_PHONE_STATE_LISTENER_REGISTERED_TIMEOUT_S, TimeUnit.SECONDS);
    166         mPhoneStateListenerThread.quit();
    167 
    168         cleanupCalls();
    169         if (!TextUtils.isEmpty(mPreviousDefaultDialer)) {
    170             TestUtils.setDefaultDialer(getInstrumentation(), mPreviousDefaultDialer);
    171         }
    172         tearDownConnectionService(TestUtils.TEST_PHONE_ACCOUNT_HANDLE);
    173         assertMockInCallServiceUnbound();
    174     }
    175 
    176     protected PhoneAccount setupConnectionService(MockConnectionService connectionService,
    177             int flags) throws Exception {
    178         if (connectionService != null) {
    179             this.connectionService = connectionService;
    180         } else {
    181             // Generate a vanilla mock connection service, if not provided.
    182             this.connectionService = new MockConnectionService();
    183         }
    184         CtsConnectionService.setUp(this.connectionService);
    185 
    186         if ((flags & FLAG_REGISTER) != 0) {
    187             mTelecomManager.registerPhoneAccount(TestUtils.TEST_PHONE_ACCOUNT);
    188         }
    189         if ((flags & FLAG_ENABLE) != 0) {
    190             TestUtils.enablePhoneAccount(getInstrumentation(), TestUtils.TEST_PHONE_ACCOUNT_HANDLE);
    191             // Wait till the adb commands have executed and account is enabled in Telecom database.
    192             assertPhoneAccountEnabled(TestUtils.TEST_PHONE_ACCOUNT_HANDLE);
    193         }
    194 
    195         return TestUtils.TEST_PHONE_ACCOUNT;
    196     }
    197 
    198     protected void tearDownConnectionService(PhoneAccountHandle accountHandle) throws Exception {
    199         if (this.connectionService != null) {
    200             assertNumConnections(this.connectionService, 0);
    201         }
    202         mTelecomManager.unregisterPhoneAccount(accountHandle);
    203         CtsConnectionService.tearDown();
    204         assertCtsConnectionServiceUnbound();
    205         this.connectionService = null;
    206     }
    207 
    208     protected void startCallTo(Uri address, PhoneAccountHandle accountHandle) {
    209         final Intent intent = new Intent(Intent.ACTION_CALL, address);
    210         if (accountHandle != null) {
    211             intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, accountHandle);
    212         }
    213         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    214         mContext.startActivity(intent);
    215     }
    216 
    217     void sleep(long ms) {
    218         try {
    219             Thread.sleep(ms);
    220         } catch (InterruptedException e) {
    221         }
    222     }
    223 
    224     private void setupCallbacks() {
    225         mInCallCallbacks = new InCallServiceCallbacks() {
    226             @Override
    227             public void onCallAdded(Call call, int numCalls) {
    228                 Log.i(TAG, "onCallAdded, Call: " + call + ", Num Calls: " + numCalls);
    229                 this.lock.release();
    230             }
    231             @Override
    232             public void onCallRemoved(Call call, int numCalls) {
    233                 Log.i(TAG, "onCallRemoved, Call: " + call + ", Num Calls: " + numCalls);
    234             }
    235             @Override
    236             public void onParentChanged(Call call, Call parent) {
    237                 Log.i(TAG, "onParentChanged, Call: " + call + ", Parent: " + parent);
    238                 this.lock.release();
    239             }
    240             @Override
    241             public void onChildrenChanged(Call call, List<Call> children) {
    242                 Log.i(TAG, "onChildrenChanged, Call: " + call + "Children: " + children);
    243                 this.lock.release();
    244             }
    245             @Override
    246             public void onConferenceableCallsChanged(Call call, List<Call> conferenceableCalls) {
    247                 Log.i(TAG, "onConferenceableCallsChanged, Call: " + call + ", Conferenceables: " +
    248                         conferenceableCalls);
    249             }
    250             @Override
    251             public void onDetailsChanged(Call call, Call.Details details) {
    252                 Log.i(TAG, "onDetailsChanged, Call: " + call + ", Details: " + details);
    253                 if (!areBundlesEqual(mPreviousExtras, details.getExtras())) {
    254                     mOnExtrasChangedCounter.invoke(call, details);
    255                 }
    256                 mPreviousExtras = details.getExtras();
    257 
    258                 if (mPreviousProperties != details.getCallProperties()) {
    259                     mOnPropertiesChangedCounter.invoke(call, details);
    260                     Log.i(TAG, "onDetailsChanged; properties changed from " + Call.Details.propertiesToString(mPreviousProperties) +
    261                             " to " + Call.Details.propertiesToString(details.getCallProperties()));
    262                 }
    263                 mPreviousProperties = details.getCallProperties();
    264             }
    265             @Override
    266             public void onCallDestroyed(Call call) {
    267                 Log.i(TAG, "onCallDestroyed, Call: " + call);
    268             }
    269             @Override
    270             public void onCallStateChanged(Call call, int newState) {
    271                 Log.i(TAG, "onCallStateChanged, Call: " + call + ", New State: " + newState);
    272             }
    273             @Override
    274             public void onBringToForeground(boolean showDialpad) {
    275                 mOnBringToForegroundCounter.invoke(showDialpad);
    276             }
    277             @Override
    278             public void onCallAudioStateChanged(CallAudioState audioState) {
    279                 Log.i(TAG, "onCallAudioStateChanged, audioState: " + audioState);
    280                 mOnCallAudioStateChangedCounter.invoke(audioState);
    281             }
    282             @Override
    283             public void onPostDialWait(Call call, String remainingPostDialSequence) {
    284                 mOnPostDialWaitCounter.invoke(call, remainingPostDialSequence);
    285             }
    286             @Override
    287             public void onCannedTextResponsesLoaded(Call call, List<String> cannedTextResponses) {
    288                 mOnCannedTextResponsesLoadedCounter.invoke(call, cannedTextResponses);
    289             }
    290             @Override
    291             public void onConnectionEvent(Call call, String event, Bundle extras) {
    292                 mOnConnectionEventCounter.invoke(call, event, extras);
    293             }
    294 
    295             @Override
    296             public void onSilenceRinger() {
    297                 Log.i(TAG, "onSilenceRinger");
    298                 mOnSilenceRingerCounter.invoke();
    299             }
    300 
    301             @Override
    302             public void onRttModeChanged(Call call, int mode) {
    303                 mOnRttModeChangedCounter.invoke(call, mode);
    304             }
    305 
    306             @Override
    307             public void onRttStatusChanged(Call call, boolean enabled, Call.RttCall rttCall) {
    308                 mOnRttStatusChangedCounter.invoke(call, enabled, rttCall);
    309             }
    310 
    311             @Override
    312             public void onRttRequest(Call call, int id) {
    313                 mOnRttRequestCounter.invoke(call, id);
    314             }
    315 
    316             @Override
    317             public void onRttInitiationFailure(Call call, int reason) {
    318                 mOnRttInitiationFailedCounter.invoke(call, reason);
    319             }
    320 
    321             @Override
    322             public void onHandoverComplete(Call call) {
    323                 mOnHandoverCompleteCounter.invoke(call);
    324             }
    325 
    326             @Override
    327             public void onHandoverFailed(Call call, int reason) {
    328                 mOnHandoverFailedCounter.invoke(call, reason);
    329             }
    330         };
    331 
    332         MockInCallService.setCallbacks(mInCallCallbacks);
    333 
    334         // TODO: If more InvokeCounters are added in the future, consider consolidating them into a
    335         // single Collection.
    336         mOnBringToForegroundCounter = new TestUtils.InvokeCounter("OnBringToForeground");
    337         mOnCallAudioStateChangedCounter = new TestUtils.InvokeCounter("OnCallAudioStateChanged");
    338         mOnPostDialWaitCounter = new TestUtils.InvokeCounter("OnPostDialWait");
    339         mOnCannedTextResponsesLoadedCounter = new TestUtils.InvokeCounter("OnCannedTextResponsesLoaded");
    340         mOnSilenceRingerCounter = new TestUtils.InvokeCounter("OnSilenceRinger");
    341         mOnConnectionEventCounter = new TestUtils.InvokeCounter("OnConnectionEvent");
    342         mOnExtrasChangedCounter = new TestUtils.InvokeCounter("OnDetailsChangedCounter");
    343         mOnPropertiesChangedCounter = new TestUtils.InvokeCounter("OnPropertiesChangedCounter");
    344         mOnRttModeChangedCounter = new TestUtils.InvokeCounter("mOnRttModeChangedCounter");
    345         mOnRttStatusChangedCounter = new TestUtils.InvokeCounter("mOnRttStatusChangedCounter");
    346         mOnRttInitiationFailedCounter =
    347                 new TestUtils.InvokeCounter("mOnRttInitiationFailedCounter");
    348         mOnRttRequestCounter = new TestUtils.InvokeCounter("mOnRttRequestCounter");
    349         mOnHandoverCompleteCounter = new TestUtils.InvokeCounter("mOnHandoverCompleteCounter");
    350         mOnHandoverFailedCounter = new TestUtils.InvokeCounter("mOnHandoverFailedCounter");
    351     }
    352 
    353     /**
    354      * Puts Telecom in a state where there is an incoming call provided by the
    355      * {@link CtsConnectionService} which can be tested.
    356      */
    357     void addAndVerifyNewIncomingCall(Uri incomingHandle, Bundle extras) {
    358         assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits());
    359         int currentCallCount = 0;
    360         if (mInCallCallbacks.getService() != null) {
    361             currentCallCount = mInCallCallbacks.getService().getCallCount();
    362         }
    363 
    364         if (extras == null) {
    365             extras = new Bundle();
    366         }
    367         extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, incomingHandle);
    368         mTelecomManager.addNewIncomingCall(TestUtils.TEST_PHONE_ACCOUNT_HANDLE, extras);
    369 
    370         try {
    371             if (!mInCallCallbacks.lock.tryAcquire(TestUtils.WAIT_FOR_CALL_ADDED_TIMEOUT_S,
    372                         TimeUnit.SECONDS)) {
    373                 fail("No call added to InCallService.");
    374             }
    375         } catch (InterruptedException e) {
    376             Log.i(TAG, "Test interrupted!");
    377         }
    378 
    379         assertEquals("InCallService should contain 1 more call after adding a call.",
    380                 currentCallCount + 1,
    381                 mInCallCallbacks.getService().getCallCount());
    382     }
    383 
    384     /**
    385      *  Puts Telecom in a state where there is an active call provided by the
    386      *  {@link CtsConnectionService} which can be tested.
    387      */
    388     void placeAndVerifyCall() {
    389         placeAndVerifyCall(null);
    390     }
    391 
    392     /**
    393      *  Puts Telecom in a state where there is an active call provided by the
    394      *  {@link CtsConnectionService} which can be tested.
    395      *
    396      *  @param videoState the video state of the call.
    397      */
    398     void placeAndVerifyCall(int videoState) {
    399         placeAndVerifyCall(null, videoState);
    400     }
    401 
    402     /**
    403      *  Puts Telecom in a state where there is an active call provided by the
    404      *  {@link CtsConnectionService} which can be tested.
    405      */
    406     void placeAndVerifyCall(Bundle extras) {
    407         placeAndVerifyCall(extras, VideoProfile.STATE_AUDIO_ONLY);
    408     }
    409 
    410     /**
    411      *  Puts Telecom in a state where there is an active call provided by the
    412      *  {@link CtsConnectionService} which can be tested.
    413      */
    414     void placeAndVerifyCall(Bundle extras, int videoState) {
    415         assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits());
    416         int currentCallCount = 0;
    417         if (mInCallCallbacks.getService() != null) {
    418             currentCallCount = mInCallCallbacks.getService().getCallCount();
    419         }
    420         int currentConnectionCount = getNumberOfConnections();
    421         placeNewCallWithPhoneAccount(extras, videoState);
    422 
    423         try {
    424             if (!mInCallCallbacks.lock.tryAcquire(TestUtils.WAIT_FOR_CALL_ADDED_TIMEOUT_S,
    425                         TimeUnit.SECONDS)) {
    426                 fail("No call added to InCallService.");
    427             }
    428         } catch (InterruptedException e) {
    429             Log.i(TAG, "Test interrupted!");
    430         }
    431 
    432         assertEquals("InCallService should contain 1 more call after adding a call.",
    433                 currentCallCount + 1,
    434                 mInCallCallbacks.getService().getCallCount());
    435 
    436         // The connectionService.lock is released in
    437         // MockConnectionService#onCreateOutgoingConnection, however the connection will not
    438         // actually be added to the list of connections in the ConnectionService until shortly
    439         // afterwards.  So there is still a potential for the lock to be released before it would
    440         // be seen by calls to ConnectionService#getAllConnections().
    441         // We will wait here until the list of connections includes one more connection to ensure
    442         // that placing the call has fully completed.
    443         final int expectedConnectionCount = currentConnectionCount + 1;
    444         assertCSConnections(expectedConnectionCount);
    445     }
    446 
    447     int getNumberOfConnections() {
    448         return CtsConnectionService.getAllConnectionsFromTelecom().size();
    449     }
    450 
    451     MockConnection verifyConnectionForOutgoingCall() {
    452         // Assuming only 1 connection present
    453         return verifyConnectionForOutgoingCall(0);
    454     }
    455 
    456     MockConnection verifyConnectionForOutgoingCall(int connectionIndex) {
    457         try {
    458             if (!connectionService.lock.tryAcquire(TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    459                     TimeUnit.MILLISECONDS)) {
    460                 fail("No outgoing call connection requested by Telecom");
    461             }
    462         } catch (InterruptedException e) {
    463             Log.i(TAG, "Test interrupted!");
    464         }
    465 
    466         assertThat("Telecom should create outgoing connection for outgoing call",
    467                 connectionService.outgoingConnections.size(), not(equalTo(0)));
    468         MockConnection connection = connectionService.outgoingConnections.get(connectionIndex);
    469         return connection;
    470     }
    471 
    472     MockConnection verifyConnectionForIncomingCall() {
    473         // Assuming only 1 connection present
    474         return verifyConnectionForIncomingCall(0);
    475     }
    476 
    477     MockConnection verifyConnectionForIncomingCall(int connectionIndex) {
    478         try {
    479             if (!connectionService.lock.tryAcquire(TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    480                     TimeUnit.MILLISECONDS)) {
    481                 fail("No outgoing call connection requested by Telecom");
    482             }
    483         } catch (InterruptedException e) {
    484             Log.i(TAG, "Test interrupted!");
    485         }
    486 
    487         assertThat("Telecom should create incoming connections for incoming calls",
    488                 connectionService.incomingConnections.size(), not(equalTo(0)));
    489         MockConnection connection = connectionService.incomingConnections.get(connectionIndex);
    490         setAndVerifyConnectionForIncomingCall(connection);
    491         return connection;
    492     }
    493 
    494     void setAndVerifyConnectionForIncomingCall(MockConnection connection) {
    495         connection.setRinging();
    496         assertConnectionState(connection, Connection.STATE_RINGING);
    497     }
    498 
    499     void setAndVerifyConferenceablesForOutgoingConnection(int connectionIndex) {
    500         assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits());
    501         // Make all other outgoing connections as conferenceable with this connection.
    502         MockConnection connection = connectionService.outgoingConnections.get(connectionIndex);
    503         List<Connection> confConnections =
    504                 new ArrayList<>(connectionService.outgoingConnections.size());
    505         for (Connection c : connectionService.outgoingConnections) {
    506             if (c != connection) {
    507                 confConnections.add(c);
    508             }
    509         }
    510         connection.setConferenceableConnections(confConnections);
    511         assertEquals(connection.getConferenceables(), confConnections);
    512     }
    513 
    514     void addConferenceCall(Call call1, Call call2) {
    515         assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits());
    516         int currentConfCallCount = 0;
    517         if (mInCallCallbacks.getService() != null) {
    518             currentConfCallCount = mInCallCallbacks.getService().getConferenceCallCount();
    519         }
    520         // Verify that the calls have each other on their conferenceable list before proceeding
    521         List<Call> callConfList = new ArrayList<>();
    522         callConfList.add(call2);
    523         assertCallConferenceableList(call1, callConfList);
    524 
    525         callConfList.clear();
    526         callConfList.add(call1);
    527         assertCallConferenceableList(call2, callConfList);
    528 
    529         call1.conference(call2);
    530 
    531         /**
    532          * We should have 1 onCallAdded, 2 onChildrenChanged and 2 onParentChanged invoked, so
    533          * we should have 5 available permits on the incallService lock.
    534          */
    535         try {
    536             if (!mInCallCallbacks.lock.tryAcquire(5, 3, TimeUnit.SECONDS)) {
    537                 fail("Conference addition failed.");
    538             }
    539         } catch (InterruptedException e) {
    540             Log.i(TAG, "Test interrupted!");
    541         }
    542 
    543         assertEquals("InCallService should contain 1 more call after adding a conf call.",
    544                 currentConfCallCount + 1,
    545                 mInCallCallbacks.getService().getConferenceCallCount());
    546     }
    547 
    548     void splitFromConferenceCall(Call call1) {
    549         assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits());
    550 
    551         call1.splitFromConference();
    552         /**
    553          * We should have 1 onChildrenChanged and 1 onParentChanged invoked, so
    554          * we should have 2 available permits on the incallService lock.
    555          */
    556         try {
    557             if (!mInCallCallbacks.lock.tryAcquire(2, 3, TimeUnit.SECONDS)) {
    558                 fail("Conference split failed");
    559             }
    560         } catch (InterruptedException e) {
    561             Log.i(TAG, "Test interrupted!");
    562         }
    563     }
    564 
    565     MockConference verifyConferenceForOutgoingCall() {
    566         try {
    567             if (!connectionService.lock.tryAcquire(TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    568                     TimeUnit.MILLISECONDS)) {
    569                 fail("No outgoing conference requested by Telecom");
    570             }
    571         } catch (InterruptedException e) {
    572             Log.i(TAG, "Test interrupted!");
    573         }
    574         // Return the newly created conference object to the caller
    575         MockConference conference = connectionService.conferences.get(0);
    576         setAndVerifyConferenceForOutgoingCall(conference);
    577         return conference;
    578     }
    579 
    580     void setAndVerifyConferenceForOutgoingCall(MockConference conference) {
    581         conference.setActive();
    582         assertConferenceState(conference, Connection.STATE_ACTIVE);
    583     }
    584 
    585     void verifyPhoneStateListenerCallbacksForCall(int expectedCallState, String expectedNumber)
    586             throws Exception {
    587         assertTrue(mPhoneStateListener.mCallbackSemaphore.tryAcquire(
    588                 TestUtils.WAIT_FOR_PHONE_STATE_LISTENER_CALLBACK_TIMEOUT_S, TimeUnit.SECONDS));
    589         // At this point we can only be sure that we got AN update, but not necessarily the one we
    590         // are looking for; wait until we see the state we want before verifying further.
    591         waitUntilConditionIsTrueOrTimeout(new Condition() {
    592                                               @Override
    593                                               public Object expected() {
    594                                                   return true;
    595                                               }
    596 
    597                                               @Override
    598                                               public Object actual() {
    599                                                   return mPhoneStateListener.mCallStates
    600                                                           .stream()
    601                                                           .filter(p -> p.first.equals(
    602                                                                   expectedCallState)
    603                                                                   && p.second.equals(
    604                                                                   expectedNumber))
    605                                                           .count() > 0;
    606                                               }
    607                                           },
    608                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    609                 "Expected call state " + expectedCallState + " and number "
    610                         + expectedNumber);
    611 
    612 
    613         // Get the most recent callback; it is possible that there was an initial state reported due
    614         // to the fact that TelephonyManager will sometimes give an initial state back to the caller
    615         // when the listener is registered.
    616         Pair<Integer, String> callState = mPhoneStateListener.mCallStates.get(
    617                 mPhoneStateListener.mCallStates.size() - 1);
    618         assertEquals(expectedCallState, (int) callState.first);
    619         // Note: We do NOT check the phone number here.  Due to changes in how the phone state
    620         // broadcast is sent, the caller may receive multiple broadcasts, and the number will be
    621         // present in one or the other.  We waited for a full matching broadcast above so we can
    622         // be sure the number was reported as expected.
    623     }
    624 
    625     /**
    626      * Disconnect the created test call and verify that Telecom has cleared all calls.
    627      */
    628     void cleanupCalls() {
    629         if (mInCallCallbacks != null && mInCallCallbacks.getService() != null) {
    630             mInCallCallbacks.getService().disconnectAllConferenceCalls();
    631             mInCallCallbacks.getService().disconnectAllCalls();
    632             assertNumConferenceCalls(mInCallCallbacks.getService(), 0);
    633             assertNumCalls(mInCallCallbacks.getService(), 0);
    634         }
    635     }
    636 
    637     /**
    638      * Place a new outgoing call via the {@link CtsConnectionService}
    639      */
    640     private void placeNewCallWithPhoneAccount(Bundle extras, int videoState) {
    641         if (extras == null) {
    642             extras = new Bundle();
    643         }
    644         if (!extras.containsKey(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE)) {
    645             extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
    646                     TestUtils.TEST_PHONE_ACCOUNT_HANDLE);
    647         }
    648 
    649         if (!VideoProfile.isAudioOnly(videoState)) {
    650             extras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState);
    651         }
    652 
    653         mTelecomManager.placeCall(createTestNumber(), extras);
    654     }
    655 
    656     /**
    657      * Create a new number each time for a new test. Telecom has special logic to reuse certain
    658      * calls if multiple calls to the same number are placed within a short period of time which
    659      * can cause certain tests to fail.
    660      */
    661     Uri createTestNumber() {
    662         return Uri.fromParts("tel", String.valueOf(++sCounter), null);
    663     }
    664 
    665     public static Uri getTestNumber() {
    666         return Uri.fromParts("tel", String.valueOf(sCounter), null);
    667     }
    668 
    669     void assertNumCalls(final MockInCallService inCallService, final int numCalls) {
    670         waitUntilConditionIsTrueOrTimeout(new Condition() {
    671             @Override
    672             public Object expected() {
    673                 return numCalls;
    674             }
    675             @Override
    676             public Object actual() {
    677                 return inCallService.getCallCount();
    678             }
    679         },
    680         WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    681         "InCallService should contain " + numCalls + " calls."
    682     );
    683     }
    684 
    685     void assertNumConferenceCalls(final MockInCallService inCallService, final int numCalls) {
    686         waitUntilConditionIsTrueOrTimeout(new Condition() {
    687             @Override
    688             public Object expected() {
    689                 return numCalls;
    690             }
    691             @Override
    692             public Object actual() {
    693                 return inCallService.getConferenceCallCount();
    694             }
    695         },
    696         WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    697         "InCallService should contain " + numCalls + " conference calls."
    698     );
    699     }
    700 
    701     void assertCSConnections(final int numConnections) {
    702         waitUntilConditionIsTrueOrTimeout(new Condition() {
    703                                               @Override
    704                                               public Object expected() {
    705                                                   return numConnections;
    706                                               }
    707 
    708                                               @Override
    709                                               public Object actual() {
    710                                                   return CtsConnectionService
    711                                                           .getAllConnectionsFromTelecom()
    712                                                           .size();
    713                                               }
    714                                           },
    715                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    716                 "ConnectionService should contain " + numConnections + " connections."
    717         );
    718     }
    719 
    720     void assertNumConnections(final MockConnectionService connService, final int numConnections) {
    721         waitUntilConditionIsTrueOrTimeout(new Condition() {
    722                                               @Override
    723                                               public Object expected() {
    724                                                   return numConnections;
    725                                               }
    726                                               @Override
    727                                               public Object actual() {
    728                                                   return connService.getAllConnections().size();
    729                                               }
    730                                           },
    731                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    732                 "ConnectionService should contain " + numConnections + " connections."
    733         );
    734     }
    735 
    736     void assertMuteState(final InCallService incallService, final boolean isMuted) {
    737         waitUntilConditionIsTrueOrTimeout(
    738                 new Condition() {
    739                     @Override
    740                     public Object expected() {
    741                         return isMuted;
    742                     }
    743 
    744                     @Override
    745                     public Object actual() {
    746                         final CallAudioState state = incallService.getCallAudioState();
    747                         return state == null ? null : state.isMuted();
    748                     }
    749                 },
    750                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    751                 "Phone's mute state should be: " + isMuted
    752         );
    753     }
    754 
    755     void assertMuteState(final MockConnection connection, final boolean isMuted) {
    756         waitUntilConditionIsTrueOrTimeout(
    757                 new Condition() {
    758                     @Override
    759                     public Object expected() {
    760                         return isMuted;
    761                     }
    762 
    763                     @Override
    764                     public Object actual() {
    765                         final CallAudioState state = connection.getCallAudioState();
    766                         return state == null ? null : state.isMuted();
    767                     }
    768                 },
    769                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    770                 "Connection's mute state should be: " + isMuted
    771         );
    772     }
    773 
    774     void assertAudioRoute(final InCallService incallService, final int route) {
    775         waitUntilConditionIsTrueOrTimeout(
    776                 new Condition() {
    777                     @Override
    778                     public Object expected() {
    779                         return route;
    780                     }
    781 
    782                     @Override
    783                     public Object actual() {
    784                         final CallAudioState state = incallService.getCallAudioState();
    785                         return state == null ? null : state.getRoute();
    786                     }
    787                 },
    788                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    789                 "Phone's audio route should be: " + route
    790         );
    791     }
    792 
    793     void assertNotAudioRoute(final InCallService incallService, final int route) {
    794         waitUntilConditionIsTrueOrTimeout(
    795                 new Condition() {
    796                     @Override
    797                     public Object expected() {
    798                         return new Boolean(true);
    799                     }
    800 
    801                     @Override
    802                     public Object actual() {
    803                         final CallAudioState state = incallService.getCallAudioState();
    804                         return route != state.getRoute();
    805                     }
    806                 },
    807                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    808                 "Phone's audio route should not be: " + route
    809         );
    810     }
    811 
    812     void assertAudioRoute(final MockConnection connection, final int route) {
    813         waitUntilConditionIsTrueOrTimeout(
    814                 new Condition() {
    815                     @Override
    816                     public Object expected() {
    817                         return route;
    818                     }
    819 
    820                     @Override
    821                     public Object actual() {
    822                         final CallAudioState state = ((Connection) connection).getCallAudioState();
    823                         return state == null ? null : state.getRoute();
    824                     }
    825                 },
    826                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    827                 "Connection's audio route should be: " + route
    828         );
    829     }
    830 
    831     void assertConnectionState(final Connection connection, final int state) {
    832         waitUntilConditionIsTrueOrTimeout(
    833                 new Condition() {
    834                     @Override
    835                     public Object expected() {
    836                         return state;
    837                     }
    838 
    839                     @Override
    840                     public Object actual() {
    841                         return connection.getState();
    842                     }
    843                 },
    844                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    845                 "Connection should be in state " + state
    846         );
    847     }
    848 
    849     void assertCallState(final Call call, final int state) {
    850         waitUntilConditionIsTrueOrTimeout(
    851                 new Condition() {
    852                     @Override
    853                     public Object expected() {
    854                         return state;
    855                     }
    856 
    857                     @Override
    858                     public Object actual() {
    859                         return call.getState();
    860                     }
    861                 },
    862                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    863                 "Call: " + call + " should be in state " + state
    864         );
    865     }
    866 
    867     void assertCallConferenceableList(final Call call, final List<Call> conferenceableList) {
    868         waitUntilConditionIsTrueOrTimeout(
    869                 new Condition() {
    870                     @Override
    871                     public Object expected() {
    872                         return conferenceableList;
    873                     }
    874 
    875                     @Override
    876                     public Object actual() {
    877                         return call.getConferenceableCalls();
    878                     }
    879                 },
    880                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    881                 "Call: " + call + " does not have the correct conferenceable call list."
    882         );
    883     }
    884 
    885     void assertDtmfString(final MockConnection connection, final String dtmfString) {
    886         waitUntilConditionIsTrueOrTimeout(new Condition() {
    887                 @Override
    888                 public Object expected() {
    889                     return dtmfString;
    890                 }
    891 
    892                 @Override
    893                 public Object actual() {
    894                     return connection.getDtmfString();
    895                 }
    896             },
    897             WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    898             "DTMF string should be equivalent to entered DTMF characters: " + dtmfString
    899         );
    900     }
    901 
    902     void assertDtmfString(final MockConference conference, final String dtmfString) {
    903         waitUntilConditionIsTrueOrTimeout(new Condition() {
    904                 @Override
    905                 public Object expected() {
    906                     return dtmfString;
    907                 }
    908 
    909                 @Override
    910                 public Object actual() {
    911                     return conference.getDtmfString();
    912                 }
    913             },
    914             WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    915             "DTMF string should be equivalent to entered DTMF characters: " + dtmfString
    916         );
    917     }
    918 
    919     void assertCallDisplayName(final Call call, final String name) {
    920         waitUntilConditionIsTrueOrTimeout(
    921                 new Condition() {
    922                     @Override
    923                     public Object expected() {
    924                         return name;
    925                     }
    926 
    927                     @Override
    928                     public Object actual() {
    929                         return call.getDetails().getCallerDisplayName();
    930                     }
    931                 },
    932                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    933                 "Call should have display name: " + name
    934         );
    935     }
    936 
    937     void assertConnectionCallDisplayName(final Connection connection, final String name) {
    938         waitUntilConditionIsTrueOrTimeout(
    939                 new Condition() {
    940                     @Override
    941                     public Object expected() {
    942                         return name;
    943                     }
    944 
    945                     @Override
    946                     public Object actual() {
    947                         return connection.getCallerDisplayName();
    948                     }
    949                 },
    950                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    951                 "Connection should have display name: " + name
    952         );
    953     }
    954 
    955     void assertDisconnectReason(final Connection connection, final String disconnectReason) {
    956         waitUntilConditionIsTrueOrTimeout(
    957                 new Condition() {
    958                     @Override
    959                     public Object expected() {
    960                         return disconnectReason;
    961                     }
    962 
    963                     @Override
    964                     public Object actual() {
    965                         return connection.getDisconnectCause().getReason();
    966                     }
    967                 },
    968                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    969                 "Connection should have been disconnected with reason: " + disconnectReason
    970         );
    971     }
    972 
    973     void assertConferenceState(final Conference conference, final int state) {
    974         waitUntilConditionIsTrueOrTimeout(
    975                 new Condition() {
    976                     @Override
    977                     public Object expected() {
    978                         return state;
    979                     }
    980 
    981                     @Override
    982                     public Object actual() {
    983                         return conference.getState();
    984                     }
    985                 },
    986                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    987                 "Conference should be in state " + state
    988         );
    989     }
    990 
    991     /**
    992      * Checks all fields of two PhoneAccounts for equality, with the exception of the enabled state.
    993      * Should only be called after assertPhoneAccountRegistered when it can be guaranteed
    994      * that the PhoneAccount is registered.
    995      * @param expected The expected PhoneAccount.
    996      * @param actual The actual PhoneAccount.
    997      */
    998     void assertPhoneAccountEquals(final PhoneAccount expected,
    999             final PhoneAccount actual) {
   1000         assertEquals(expected.getAddress(), actual.getAddress());
   1001         assertEquals(expected.getAccountHandle(), actual.getAccountHandle());
   1002         assertEquals(expected.getCapabilities(), actual.getCapabilities());
   1003         assertTrue(areBundlesEqual(expected.getExtras(), actual.getExtras()));
   1004         assertEquals(expected.getHighlightColor(), actual.getHighlightColor());
   1005         assertEquals(expected.getIcon(), actual.getIcon());
   1006         assertEquals(expected.getLabel(), actual.getLabel());
   1007         assertEquals(expected.getShortDescription(), actual.getShortDescription());
   1008         assertEquals(expected.getSubscriptionAddress(), actual.getSubscriptionAddress());
   1009         assertEquals(expected.getSupportedUriSchemes(), actual.getSupportedUriSchemes());
   1010     }
   1011 
   1012     void assertPhoneAccountRegistered(final PhoneAccountHandle handle) {
   1013         waitUntilConditionIsTrueOrTimeout(
   1014                 new Condition() {
   1015                     @Override
   1016                     public Object expected() {
   1017                         return true;
   1018                     }
   1019 
   1020                     @Override
   1021                     public Object actual() {
   1022                         return mTelecomManager.getPhoneAccount(handle) != null;
   1023                     }
   1024                 },
   1025                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
   1026                 "Phone account registration failed for " + handle
   1027         );
   1028     }
   1029 
   1030     void assertPhoneAccountEnabled(final PhoneAccountHandle handle) {
   1031         waitUntilConditionIsTrueOrTimeout(
   1032                 new Condition() {
   1033                     @Override
   1034                     public Object expected() {
   1035                         return true;
   1036                     }
   1037 
   1038                     @Override
   1039                     public Object actual() {
   1040                         PhoneAccount phoneAccount = mTelecomManager.getPhoneAccount(handle);
   1041                         return (phoneAccount != null && phoneAccount.isEnabled());
   1042                     }
   1043                 },
   1044                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
   1045                 "Phone account enable failed for " + handle
   1046         );
   1047     }
   1048 
   1049     void assertCtsConnectionServiceUnbound() {
   1050         if (CtsConnectionService.isBound()) {
   1051             assertTrue("CtsConnectionService not yet unbound!",
   1052                     CtsConnectionService.waitForUnBinding());
   1053         }
   1054     }
   1055 
   1056     void assertMockInCallServiceUnbound() {
   1057         waitUntilConditionIsTrueOrTimeout(
   1058                 new Condition() {
   1059                     @Override
   1060                     public Object expected() {
   1061                         return false;
   1062                     }
   1063 
   1064                     @Override
   1065                     public Object actual() {
   1066                         return MockInCallService.isServiceBound();
   1067                     }
   1068                 },
   1069                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
   1070                 "MockInCallService not yet unbound!"
   1071         );
   1072     }
   1073 
   1074     void assertIsInCall(boolean isIncall) {
   1075         waitUntilConditionIsTrueOrTimeout(
   1076                 new Condition() {
   1077                     @Override
   1078                     public Object expected() {
   1079                         return isIncall;
   1080                     }
   1081 
   1082                     @Override
   1083                     public Object actual() {
   1084                         return mTelecomManager.isInCall();
   1085                     }
   1086                 },
   1087                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
   1088                 "Expected isInCall to be " + isIncall
   1089         );
   1090     }
   1091 
   1092     void assertIsInManagedCall(boolean isIncall) {
   1093         waitUntilConditionIsTrueOrTimeout(
   1094                 new Condition() {
   1095                     @Override
   1096                     public Object expected() {
   1097                         return isIncall;
   1098                     }
   1099 
   1100                     @Override
   1101                     public Object actual() {
   1102                         return mTelecomManager.isInManagedCall();
   1103                     }
   1104                 },
   1105                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
   1106                 "Expected isInManagedCall to be " + isIncall
   1107         );
   1108     }
   1109 
   1110     /**
   1111      * Asserts that a call's properties are as expected.
   1112      *
   1113      * @param call The call.
   1114      * @param properties The expected properties.
   1115      */
   1116     public void assertCallProperties(final Call call, final int properties) {
   1117         waitUntilConditionIsTrueOrTimeout(
   1118                 new Condition() {
   1119                     @Override
   1120                     public Object expected() {
   1121                         return true;
   1122                     }
   1123 
   1124                     @Override
   1125                     public Object actual() {
   1126                         return call.getDetails().hasProperty(properties);
   1127                     }
   1128                 },
   1129                 TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
   1130                 "Call should have properties " + properties
   1131         );
   1132     }
   1133 
   1134     /**
   1135      * Asserts that a call's capabilities are as expected.
   1136      *
   1137      * @param call The call.
   1138      * @param capabilities The expected capabiltiies.
   1139      */
   1140     public void assertCallCapabilities(final Call call, final int capabilities) {
   1141         waitUntilConditionIsTrueOrTimeout(
   1142                 new Condition() {
   1143                     @Override
   1144                     public Object expected() {
   1145                         return true;
   1146                     }
   1147 
   1148                     @Override
   1149                     public Object actual() {
   1150                         return (call.getDetails().getCallCapabilities() & capabilities) ==
   1151                                 capabilities;
   1152                     }
   1153                 },
   1154                 TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
   1155                 "Call should have properties " + capabilities
   1156         );
   1157     }
   1158 
   1159     void waitUntilConditionIsTrueOrTimeout(Condition condition, long timeout,
   1160             String description) {
   1161         final long start = System.currentTimeMillis();
   1162         while (!condition.expected().equals(condition.actual())
   1163                 && System.currentTimeMillis() - start < timeout) {
   1164             sleep(50);
   1165         }
   1166         assertEquals(description, condition.expected(), condition.actual());
   1167     }
   1168 
   1169     /**
   1170      * Performs some work, and waits for the condition to be met.  If the condition is not met in
   1171      * each step of the loop, the work is performed again.
   1172      *
   1173      * @param work The work to perform.
   1174      * @param condition The condition.
   1175      * @param timeout The timeout.
   1176      * @param description Description of the work being performed.
   1177      */
   1178     void doWorkAndWaitUntilConditionIsTrueOrTimeout(Work work, Condition condition, long timeout,
   1179             String description) {
   1180         final long start = System.currentTimeMillis();
   1181         work.doWork();
   1182         while (!condition.expected().equals(condition.actual())
   1183                 && System.currentTimeMillis() - start < timeout) {
   1184             sleep(50);
   1185             work.doWork();
   1186         }
   1187         assertEquals(description, condition.expected(), condition.actual());
   1188     }
   1189 
   1190     protected interface Condition {
   1191         Object expected();
   1192         Object actual();
   1193     }
   1194 
   1195     protected interface Work {
   1196         void doWork();
   1197     }
   1198 
   1199     public static boolean areBundlesEqual(Bundle extras, Bundle newExtras) {
   1200         if (extras == null || newExtras == null) {
   1201             return extras == newExtras;
   1202         }
   1203 
   1204         if (extras.size() != newExtras.size()) {
   1205             return false;
   1206         }
   1207 
   1208         for (String key : extras.keySet()) {
   1209             if (key != null) {
   1210                 final Object value = extras.get(key);
   1211                 final Object newValue = newExtras.get(key);
   1212                 if (!Objects.equals(value, newValue)) {
   1213                     return false;
   1214                 }
   1215             }
   1216         }
   1217         return true;
   1218     }
   1219 }
   1220