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.*;
     20 
     21 import static org.hamcrest.CoreMatchers.not;
     22 import static org.hamcrest.CoreMatchers.equalTo;
     23 import static org.junit.Assert.assertThat;
     24 
     25 import android.content.ComponentName;
     26 import android.content.Context;
     27 import android.content.Intent;
     28 import android.graphics.Color;
     29 import android.net.Uri;
     30 import android.os.Bundle;
     31 import android.os.SystemClock;
     32 import android.telecom.Call;
     33 import android.telecom.CallAudioState;
     34 import android.telecom.Conference;
     35 import android.telecom.Connection;
     36 import android.telecom.InCallService;
     37 import android.telecom.PhoneAccount;
     38 import android.telecom.PhoneAccountHandle;
     39 import android.telecom.TelecomManager;
     40 import android.telecom.VideoProfile;
     41 import android.telecom.cts.MockInCallService.InCallServiceCallbacks;
     42 import android.test.InstrumentationTestCase;
     43 import android.text.TextUtils;
     44 import android.util.Log;
     45 
     46 import java.util.ArrayList;
     47 import java.util.List;
     48 import java.util.concurrent.TimeUnit;
     49 
     50 /**
     51  * Base class for Telecom CTS tests that require a {@link CtsConnectionService} and
     52  * {@link MockInCallService} to verify Telecom functionality.
     53  */
     54 public class BaseTelecomTestWithMockServices extends InstrumentationTestCase {
     55 
     56     public static final int FLAG_REGISTER = 0x1;
     57     public static final int FLAG_ENABLE = 0x2;
     58 
     59     public static final PhoneAccountHandle TEST_PHONE_ACCOUNT_HANDLE =
     60             new PhoneAccountHandle(new ComponentName(PACKAGE, COMPONENT), ACCOUNT_ID);
     61 
     62     public static final PhoneAccount TEST_PHONE_ACCOUNT = PhoneAccount.builder(
     63             TEST_PHONE_ACCOUNT_HANDLE, ACCOUNT_LABEL)
     64             .setAddress(Uri.parse("tel:555-TEST"))
     65             .setSubscriptionAddress(Uri.parse("tel:555-TEST"))
     66             .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER |
     67                     PhoneAccount.CAPABILITY_VIDEO_CALLING |
     68                     PhoneAccount.CAPABILITY_CONNECTION_MANAGER)
     69             .setHighlightColor(Color.RED)
     70             .setShortDescription(ACCOUNT_LABEL)
     71             .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
     72             .addSupportedUriScheme(PhoneAccount.SCHEME_VOICEMAIL)
     73             .build();
     74 
     75     private static int sCounter = 9999;
     76 
     77     Context mContext;
     78     TelecomManager mTelecomManager;
     79 
     80     InvokeCounter mOnBringToForegroundCounter;
     81     InvokeCounter mOnCallAudioStateChangedCounter;
     82     InvokeCounter mOnPostDialWaitCounter;
     83     InvokeCounter mOnCannedTextResponsesLoadedCounter;
     84 
     85     InCallServiceCallbacks mInCallCallbacks;
     86     String mPreviousDefaultDialer = null;
     87     MockConnectionService connectionService = null;
     88 
     89     boolean mShouldTestTelecom = true;
     90 
     91     @Override
     92     protected void setUp() throws Exception {
     93         super.setUp();
     94         mContext = getInstrumentation().getContext();
     95         mTelecomManager = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
     96 
     97         mShouldTestTelecom = shouldTestTelecom(mContext);
     98         if (mShouldTestTelecom) {
     99             mPreviousDefaultDialer = TestUtils.getDefaultDialer(getInstrumentation());
    100             TestUtils.setDefaultDialer(getInstrumentation(), PACKAGE);
    101             setupCallbacks();
    102         }
    103     }
    104 
    105     @Override
    106     protected void tearDown() throws Exception {
    107         if (mShouldTestTelecom) {
    108             cleanupCalls();
    109             if (!TextUtils.isEmpty(mPreviousDefaultDialer)) {
    110                 TestUtils.setDefaultDialer(getInstrumentation(), mPreviousDefaultDialer);
    111             }
    112             tearDownConnectionService(TEST_PHONE_ACCOUNT_HANDLE);
    113             assertMockInCallServiceUnbound();
    114         }
    115         super.tearDown();
    116     }
    117 
    118     protected PhoneAccount setupConnectionService(MockConnectionService connectionService,
    119             int flags) throws Exception {
    120         if (connectionService != null) {
    121             this.connectionService = connectionService;
    122         } else {
    123             // Generate a vanilla mock connection service, if not provided.
    124             this.connectionService = new MockConnectionService();
    125         }
    126         CtsConnectionService.setUp(TEST_PHONE_ACCOUNT_HANDLE, this.connectionService);
    127 
    128         if ((flags & FLAG_REGISTER) != 0) {
    129             mTelecomManager.registerPhoneAccount(TEST_PHONE_ACCOUNT);
    130         }
    131         if ((flags & FLAG_ENABLE) != 0) {
    132             TestUtils.enablePhoneAccount(getInstrumentation(), TEST_PHONE_ACCOUNT_HANDLE);
    133             // Wait till the adb commands have executed and account is enabled in Telecom database.
    134             assertPhoneAccountEnabled(TEST_PHONE_ACCOUNT_HANDLE);
    135         }
    136 
    137         return TEST_PHONE_ACCOUNT;
    138     }
    139 
    140     protected void tearDownConnectionService(PhoneAccountHandle accountHandle) throws Exception {
    141         assertNumConnections(this.connectionService, 0);
    142         mTelecomManager.unregisterPhoneAccount(accountHandle);
    143         CtsConnectionService.tearDown();
    144         assertCtsConnectionServiceUnbound();
    145         this.connectionService = null;
    146     }
    147 
    148     protected void startCallTo(Uri address, PhoneAccountHandle accountHandle) {
    149         final Intent intent = new Intent(Intent.ACTION_CALL, address);
    150         if (accountHandle != null) {
    151             intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, accountHandle);
    152         }
    153         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    154         mContext.startActivity(intent);
    155     }
    156 
    157     private void sleep(long ms) {
    158         try {
    159             Thread.sleep(ms);
    160         } catch (InterruptedException e) {
    161         }
    162     }
    163 
    164     private void setupCallbacks() {
    165         mInCallCallbacks = new InCallServiceCallbacks() {
    166             @Override
    167             public void onCallAdded(Call call, int numCalls) {
    168                 Log.i(TAG, "onCallAdded, Call: " + call + ", Num Calls: " + numCalls);
    169                 this.lock.release();
    170             }
    171             @Override
    172             public void onCallRemoved(Call call, int numCalls) {
    173                 Log.i(TAG, "onCallRemoved, Call: " + call + ", Num Calls: " + numCalls);
    174             }
    175             @Override
    176             public void onParentChanged(Call call, Call parent) {
    177                 Log.i(TAG, "onParentChanged, Call: " + call + ", Parent: " + parent);
    178                 this.lock.release();
    179             }
    180             @Override
    181             public void onChildrenChanged(Call call, List<Call> children) {
    182                 Log.i(TAG, "onChildrenChanged, Call: " + call + "Children: " + children);
    183                 this.lock.release();
    184             }
    185             @Override
    186             public void onConferenceableCallsChanged(Call call, List<Call> conferenceableCalls) {
    187                 Log.i(TAG, "onConferenceableCallsChanged, Call: " + call + ", Conferenceables: " +
    188                         conferenceableCalls);
    189             }
    190             @Override
    191             public void onDetailsChanged(Call call, Call.Details details) {
    192                 Log.i(TAG, "onDetailsChanged, Call: " + call + ", Details: " + details);
    193             }
    194             @Override
    195             public void onCallDestroyed(Call call) {
    196                 Log.i(TAG, "onCallDestroyed, Call: " + call);
    197             }
    198             @Override
    199             public void onCallStateChanged(Call call, int newState) {
    200                 Log.i(TAG, "onCallStateChanged, Call: " + call + ", New State: " + newState);
    201             }
    202             @Override
    203             public void onBringToForeground(boolean showDialpad) {
    204                 mOnBringToForegroundCounter.invoke(showDialpad);
    205             }
    206             @Override
    207             public void onCallAudioStateChanged(CallAudioState audioState) {
    208                 Log.i(TAG, "onCallAudioStateChanged, audioState: " + audioState);
    209                 mOnCallAudioStateChangedCounter.invoke(audioState);
    210             }
    211             @Override
    212             public void onPostDialWait(Call call, String remainingPostDialSequence) {
    213                 mOnPostDialWaitCounter.invoke(call, remainingPostDialSequence);
    214             }
    215             @Override
    216             public void onCannedTextResponsesLoaded(Call call, List<String> cannedTextResponses) {
    217                 mOnCannedTextResponsesLoadedCounter.invoke(call, cannedTextResponses);
    218             }
    219         };
    220 
    221         MockInCallService.setCallbacks(mInCallCallbacks);
    222 
    223         // TODO: If more InvokeCounters are added in the future, consider consolidating them into a
    224         // single Collection.
    225         mOnBringToForegroundCounter = new InvokeCounter("OnBringToForeground");
    226         mOnCallAudioStateChangedCounter = new InvokeCounter("OnCallAudioStateChanged");
    227         mOnPostDialWaitCounter = new InvokeCounter("OnPostDialWait");
    228         mOnCannedTextResponsesLoadedCounter = new InvokeCounter("OnCannedTextResponsesLoaded");
    229     }
    230 
    231     /**
    232      * Puts Telecom in a state where there is an incoming call provided by the
    233      * {@link CtsConnectionService} which can be tested.
    234      */
    235     void addAndVerifyNewIncomingCall(Uri incomingHandle, Bundle extras) {
    236         assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits());
    237         int currentCallCount = 0;
    238         if (mInCallCallbacks.getService() != null) {
    239             currentCallCount = mInCallCallbacks.getService().getCallCount();
    240         }
    241 
    242         if (extras == null) {
    243             extras = new Bundle();
    244         }
    245         extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, incomingHandle);
    246         mTelecomManager.addNewIncomingCall(TEST_PHONE_ACCOUNT_HANDLE, extras);
    247 
    248         try {
    249             if (!mInCallCallbacks.lock.tryAcquire(3, TimeUnit.SECONDS)) {
    250                 fail("No call added to InCallService.");
    251             }
    252         } catch (InterruptedException e) {
    253             Log.i(TAG, "Test interrupted!");
    254         }
    255 
    256         assertEquals("InCallService should contain 1 more call after adding a call.",
    257                 currentCallCount + 1,
    258                 mInCallCallbacks.getService().getCallCount());
    259     }
    260 
    261     /**
    262      *  Puts Telecom in a state where there is an active call provided by the
    263      *  {@link CtsConnectionService} which can be tested.
    264      */
    265     void placeAndVerifyCall() {
    266         placeAndVerifyCall(null);
    267     }
    268 
    269     /**
    270      *  Puts Telecom in a state where there is an active call provided by the
    271      *  {@link CtsConnectionService} which can be tested.
    272      *
    273      *  @param videoState the video state of the call.
    274      */
    275     void placeAndVerifyCall(int videoState) {
    276         placeAndVerifyCall(null, videoState);
    277     }
    278 
    279     /**
    280      *  Puts Telecom in a state where there is an active call provided by the
    281      *  {@link CtsConnectionService} which can be tested.
    282      */
    283     void placeAndVerifyCall(Bundle extras) {
    284         placeAndVerifyCall(extras, VideoProfile.STATE_AUDIO_ONLY);
    285     }
    286 
    287     /**
    288      *  Puts Telecom in a state where there is an active call provided by the
    289      *  {@link CtsConnectionService} which can be tested.
    290      */
    291     void placeAndVerifyCall(Bundle extras, int videoState) {
    292         assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits());
    293         int currentCallCount = 0;
    294         if (mInCallCallbacks.getService() != null) {
    295             currentCallCount = mInCallCallbacks.getService().getCallCount();
    296         }
    297         placeNewCallWithPhoneAccount(extras, videoState);
    298 
    299         try {
    300             if (!mInCallCallbacks.lock.tryAcquire(3, TimeUnit.SECONDS)) {
    301                 fail("No call added to InCallService.");
    302             }
    303         } catch (InterruptedException e) {
    304             Log.i(TAG, "Test interrupted!");
    305         }
    306 
    307         assertEquals("InCallService should contain 1 more call after adding a call.",
    308                 currentCallCount + 1,
    309                 mInCallCallbacks.getService().getCallCount());
    310     }
    311 
    312     MockConnection verifyConnectionForOutgoingCall() {
    313         // Assuming only 1 connection present
    314         return verifyConnectionForOutgoingCall(0);
    315     }
    316 
    317     MockConnection verifyConnectionForOutgoingCall(int connectionIndex) {
    318         try {
    319             if (!connectionService.lock.tryAcquire(TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    320                     TimeUnit.MILLISECONDS)) {
    321                 fail("No outgoing call connection requested by Telecom");
    322             }
    323         } catch (InterruptedException e) {
    324             Log.i(TAG, "Test interrupted!");
    325         }
    326 
    327         assertThat("Telecom should create outgoing connection for outgoing call",
    328                 connectionService.outgoingConnections.size(), not(equalTo(0)));
    329         MockConnection connection = connectionService.outgoingConnections.get(connectionIndex);
    330         return connection;
    331     }
    332 
    333     MockConnection verifyConnectionForIncomingCall() {
    334         // Assuming only 1 connection present
    335         return verifyConnectionForIncomingCall(0);
    336     }
    337 
    338     MockConnection verifyConnectionForIncomingCall(int connectionIndex) {
    339         try {
    340             if (!connectionService.lock.tryAcquire(TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    341                     TimeUnit.MILLISECONDS)) {
    342                 fail("No outgoing call connection requested by Telecom");
    343             }
    344         } catch (InterruptedException e) {
    345             Log.i(TAG, "Test interrupted!");
    346         }
    347 
    348         assertThat("Telecom should create incoming connections for incoming calls",
    349                 connectionService.incomingConnections.size(), not(equalTo(0)));
    350         MockConnection connection = connectionService.incomingConnections.get(connectionIndex);
    351         setAndVerifyConnectionForIncomingCall(connection);
    352         return connection;
    353     }
    354 
    355     void setAndVerifyConnectionForIncomingCall(MockConnection connection) {
    356         connection.setRinging();
    357         assertConnectionState(connection, Connection.STATE_RINGING);
    358     }
    359 
    360     void setAndVerifyConferenceablesForOutgoingConnection(int connectionIndex) {
    361         assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits());
    362         // Make all other outgoing connections as conferenceable with this connection.
    363         MockConnection connection = connectionService.outgoingConnections.get(connectionIndex);
    364         List<Connection> confConnections =
    365                 new ArrayList<>(connectionService.outgoingConnections.size());
    366         for (Connection c : connectionService.outgoingConnections) {
    367             if (c != connection) {
    368                 confConnections.add(c);
    369             }
    370         }
    371         connection.setConferenceableConnections(confConnections);
    372         assertEquals(connection.getConferenceables(), confConnections);
    373     }
    374 
    375     void addConferenceCall(Call call1, Call call2) {
    376         assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits());
    377         int currentConfCallCount = 0;
    378         if (mInCallCallbacks.getService() != null) {
    379             currentConfCallCount = mInCallCallbacks.getService().getConferenceCallCount();
    380         }
    381         // Verify that the calls have each other on their conferenceable list before proceeding
    382         List<Call> callConfList = new ArrayList<>();
    383         callConfList.add(call2);
    384         assertCallConferenceableList(call1, callConfList);
    385 
    386         callConfList.clear();
    387         callConfList.add(call1);
    388         assertCallConferenceableList(call2, callConfList);
    389 
    390         call1.conference(call2);
    391 
    392         /**
    393          * We should have 1 onCallAdded, 2 onChildrenChanged and 2 onParentChanged invoked, so
    394          * we should have 5 available permits on the incallService lock.
    395          */
    396         try {
    397             if (!mInCallCallbacks.lock.tryAcquire(5, 3, TimeUnit.SECONDS)) {
    398                 fail("Conference addition failed.");
    399             }
    400         } catch (InterruptedException e) {
    401             Log.i(TAG, "Test interrupted!");
    402         }
    403 
    404         assertEquals("InCallService should contain 1 more call after adding a conf call.",
    405                 currentConfCallCount + 1,
    406                 mInCallCallbacks.getService().getConferenceCallCount());
    407     }
    408 
    409     void splitFromConferenceCall(Call call1) {
    410         assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits());
    411 
    412         call1.splitFromConference();
    413         /**
    414          * We should have 1 onChildrenChanged and 1 onParentChanged invoked, so
    415          * we should have 2 available permits on the incallService lock.
    416          */
    417         try {
    418             if (!mInCallCallbacks.lock.tryAcquire(2, 3, TimeUnit.SECONDS)) {
    419                 fail("Conference split failed");
    420             }
    421         } catch (InterruptedException e) {
    422             Log.i(TAG, "Test interrupted!");
    423         }
    424     }
    425 
    426     MockConference verifyConferenceForOutgoingCall() {
    427         try {
    428             if (!connectionService.lock.tryAcquire(TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    429                     TimeUnit.MILLISECONDS)) {
    430                 fail("No outgoing conference requested by Telecom");
    431             }
    432         } catch (InterruptedException e) {
    433             Log.i(TAG, "Test interrupted!");
    434         }
    435         // Return the newly created conference object to the caller
    436         MockConference conference = connectionService.conferences.get(0);
    437         setAndVerifyConferenceForOutgoingCall(conference);
    438         return conference;
    439     }
    440 
    441     void setAndVerifyConferenceForOutgoingCall(MockConference conference) {
    442         conference.setActive();
    443         assertConferenceState(conference, Connection.STATE_ACTIVE);
    444     }
    445 
    446     /**
    447      * Disconnect the created test call and verify that Telecom has cleared all calls.
    448      */
    449     void cleanupCalls() {
    450         if (mInCallCallbacks != null && mInCallCallbacks.getService() != null) {
    451             mInCallCallbacks.getService().disconnectAllConferenceCalls();
    452             mInCallCallbacks.getService().disconnectAllCalls();
    453             assertNumConferenceCalls(mInCallCallbacks.getService(), 0);
    454             assertNumCalls(mInCallCallbacks.getService(), 0);
    455         }
    456     }
    457 
    458     /**
    459      * Place a new outgoing call via the {@link CtsConnectionService}
    460      */
    461     private void placeNewCallWithPhoneAccount(Bundle extras, int videoState) {
    462         if (extras == null) {
    463             extras = new Bundle();
    464         }
    465         extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEST_PHONE_ACCOUNT_HANDLE);
    466 
    467         if (!VideoProfile.isAudioOnly(videoState)) {
    468             extras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState);
    469         }
    470 
    471         mTelecomManager.placeCall(createTestNumber(), extras);
    472     }
    473 
    474     /**
    475      * Create a new number each time for a new test. Telecom has special logic to reuse certain
    476      * calls if multiple calls to the same number are placed within a short period of time which
    477      * can cause certain tests to fail.
    478      */
    479     Uri createTestNumber() {
    480         return Uri.fromParts("tel", String.valueOf(++sCounter), null);
    481     }
    482 
    483     public static Uri getTestNumber() {
    484         return Uri.fromParts("tel", String.valueOf(sCounter), null);
    485     }
    486 
    487     void assertNumCalls(final MockInCallService inCallService, final int numCalls) {
    488         waitUntilConditionIsTrueOrTimeout(new Condition() {
    489             @Override
    490             public Object expected() {
    491                 return numCalls;
    492             }
    493             @Override
    494             public Object actual() {
    495                 return inCallService.getCallCount();
    496             }
    497         },
    498         WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    499         "InCallService should contain " + numCalls + " calls."
    500     );
    501     }
    502 
    503     void assertNumConferenceCalls(final MockInCallService inCallService, final int numCalls) {
    504         waitUntilConditionIsTrueOrTimeout(new Condition() {
    505             @Override
    506             public Object expected() {
    507                 return numCalls;
    508             }
    509             @Override
    510             public Object actual() {
    511                 return inCallService.getConferenceCallCount();
    512             }
    513         },
    514         WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    515         "InCallService should contain " + numCalls + " conference calls."
    516     );
    517     }
    518 
    519 
    520     void assertNumConnections(final MockConnectionService connService, final int numConnections) {
    521         waitUntilConditionIsTrueOrTimeout(new Condition() {
    522                                               @Override
    523                                               public Object expected() {
    524                                                   return numConnections;
    525                                               }
    526                                               @Override
    527                                               public Object actual() {
    528                                                   return connService.getAllConnections().size();
    529                                               }
    530                                           },
    531                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    532                 "ConnectionService should contain " + numConnections + " connections."
    533         );
    534     }
    535 
    536     void assertMuteState(final InCallService incallService, final boolean isMuted) {
    537         waitUntilConditionIsTrueOrTimeout(
    538                 new Condition() {
    539                     @Override
    540                     public Object expected() {
    541                         return isMuted;
    542                     }
    543 
    544                     @Override
    545                     public Object actual() {
    546                         final CallAudioState state = incallService.getCallAudioState();
    547                         return state == null ? null : state.isMuted();
    548                     }
    549                 },
    550                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    551                 "Phone's mute state should be: " + isMuted
    552         );
    553     }
    554 
    555     void assertMuteState(final MockConnection connection, final boolean isMuted) {
    556         waitUntilConditionIsTrueOrTimeout(
    557                 new Condition() {
    558                     @Override
    559                     public Object expected() {
    560                         return isMuted;
    561                     }
    562 
    563                     @Override
    564                     public Object actual() {
    565                         final CallAudioState state = connection.getCallAudioState();
    566                         return state == null ? null : state.isMuted();
    567                     }
    568                 },
    569                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    570                 "Connection's mute state should be: " + isMuted
    571         );
    572     }
    573 
    574     void assertAudioRoute(final InCallService incallService, final int route) {
    575         waitUntilConditionIsTrueOrTimeout(
    576                 new Condition() {
    577                     @Override
    578                     public Object expected() {
    579                         return route;
    580                     }
    581 
    582                     @Override
    583                     public Object actual() {
    584                         final CallAudioState state = incallService.getCallAudioState();
    585                         return state == null ? null : state.getRoute();
    586                     }
    587                 },
    588                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    589                 "Phone's audio route should be: " + route
    590         );
    591     }
    592 
    593     void assertAudioRoute(final MockConnection connection, final int route) {
    594         waitUntilConditionIsTrueOrTimeout(
    595                 new Condition() {
    596                     @Override
    597                     public Object expected() {
    598                         return route;
    599                     }
    600 
    601                     @Override
    602                     public Object actual() {
    603                         final CallAudioState state = ((Connection) connection).getCallAudioState();
    604                         return state == null ? null : state.getRoute();
    605                     }
    606                 },
    607                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    608                 "Connection's audio route should be: " + route
    609         );
    610     }
    611 
    612     void assertConnectionState(final Connection connection, final int state) {
    613         waitUntilConditionIsTrueOrTimeout(
    614                 new Condition() {
    615                     @Override
    616                     public Object expected() {
    617                         return state;
    618                     }
    619 
    620                     @Override
    621                     public Object actual() {
    622                         return connection.getState();
    623                     }
    624                 },
    625                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    626                 "Connection should be in state " + state
    627         );
    628     }
    629 
    630     void assertCallState(final Call call, final int state) {
    631         waitUntilConditionIsTrueOrTimeout(
    632                 new Condition() {
    633                     @Override
    634                     public Object expected() {
    635                         return state;
    636                     }
    637 
    638                     @Override
    639                     public Object actual() {
    640                         return call.getState();
    641                     }
    642                 },
    643                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    644                 "Call: " + call + " should be in state " + state
    645         );
    646     }
    647 
    648     void assertCallConferenceableList(final Call call, final List<Call> conferenceableList) {
    649         waitUntilConditionIsTrueOrTimeout(
    650                 new Condition() {
    651                     @Override
    652                     public Object expected() {
    653                         return conferenceableList;
    654                     }
    655 
    656                     @Override
    657                     public Object actual() {
    658                         return call.getConferenceableCalls();
    659                     }
    660                 },
    661                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    662                 "Call: " + call + " does not have the correct conferenceable call list."
    663         );
    664     }
    665 
    666     void assertDtmfString(final MockConnection connection, final String dtmfString) {
    667         waitUntilConditionIsTrueOrTimeout(new Condition() {
    668                 @Override
    669                 public Object expected() {
    670                     return dtmfString;
    671                 }
    672 
    673                 @Override
    674                 public Object actual() {
    675                     return connection.getDtmfString();
    676                 }
    677             },
    678             WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    679             "DTMF string should be equivalent to entered DTMF characters: " + dtmfString
    680         );
    681     }
    682 
    683     void assertDtmfString(final MockConference conference, final String dtmfString) {
    684         waitUntilConditionIsTrueOrTimeout(new Condition() {
    685                 @Override
    686                 public Object expected() {
    687                     return dtmfString;
    688                 }
    689 
    690                 @Override
    691                 public Object actual() {
    692                     return conference.getDtmfString();
    693                 }
    694             },
    695             WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    696             "DTMF string should be equivalent to entered DTMF characters: " + dtmfString
    697         );
    698     }
    699 
    700     void assertCallDisplayName(final Call call, final String name) {
    701         waitUntilConditionIsTrueOrTimeout(
    702                 new Condition() {
    703                     @Override
    704                     public Object expected() {
    705                         return name;
    706                     }
    707 
    708                     @Override
    709                     public Object actual() {
    710                         return call.getDetails().getCallerDisplayName();
    711                     }
    712                 },
    713                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    714                 "Call should have display name: " + name
    715         );
    716     }
    717 
    718     void assertConnectionCallDisplayName(final Connection connection, final String name) {
    719         waitUntilConditionIsTrueOrTimeout(
    720                 new Condition() {
    721                     @Override
    722                     public Object expected() {
    723                         return name;
    724                     }
    725 
    726                     @Override
    727                     public Object actual() {
    728                         return connection.getCallerDisplayName();
    729                     }
    730                 },
    731                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    732                 "Connection should have display name: " + name
    733         );
    734     }
    735 
    736     void assertConferenceState(final Conference conference, final int state) {
    737         waitUntilConditionIsTrueOrTimeout(
    738                 new Condition() {
    739                     @Override
    740                     public Object expected() {
    741                         return state;
    742                     }
    743 
    744                     @Override
    745                     public Object actual() {
    746                         return conference.getState();
    747                     }
    748                 },
    749                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    750                 "Conference should be in state " + state
    751         );
    752     }
    753 
    754     void assertPhoneAccountRegistered(final PhoneAccountHandle handle) {
    755         waitUntilConditionIsTrueOrTimeout(
    756                 new Condition() {
    757                     @Override
    758                     public Object expected() {
    759                         return true;
    760                     }
    761 
    762                     @Override
    763                     public Object actual() {
    764                         return mTelecomManager.getPhoneAccount(handle) != null;
    765                     }
    766                 },
    767                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    768                 "Phone account registration failed for " + handle
    769         );
    770     }
    771 
    772     void assertPhoneAccountEnabled(final PhoneAccountHandle handle) {
    773         waitUntilConditionIsTrueOrTimeout(
    774                 new Condition() {
    775                     @Override
    776                     public Object expected() {
    777                         return true;
    778                     }
    779 
    780                     @Override
    781                     public Object actual() {
    782                         PhoneAccount phoneAccount = mTelecomManager.getPhoneAccount(handle);
    783                         return (phoneAccount != null && phoneAccount.isEnabled());
    784                     }
    785                 },
    786                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    787                 "Phone account enable failed for " + handle
    788         );
    789     }
    790 
    791     void assertCtsConnectionServiceUnbound() {
    792         waitUntilConditionIsTrueOrTimeout(
    793                 new Condition() {
    794                     @Override
    795                     public Object expected() {
    796                         return true;
    797                     }
    798 
    799                     @Override
    800                     public Object actual() {
    801                         return CtsConnectionService.isServiceUnbound();
    802                     }
    803                 },
    804                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    805                 "CtsConnectionService not yet unbound!"
    806         );
    807     }
    808 
    809     void assertMockInCallServiceUnbound() {
    810         waitUntilConditionIsTrueOrTimeout(
    811                 new Condition() {
    812                     @Override
    813                     public Object expected() {
    814                         return true;
    815                     }
    816 
    817                     @Override
    818                     public Object actual() {
    819                         return MockInCallService.isServiceUnbound();
    820                     }
    821                 },
    822                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
    823                 "MockInCallService not yet unbound!"
    824         );
    825     }
    826 
    827     void waitUntilConditionIsTrueOrTimeout(Condition condition, long timeout,
    828             String description) {
    829         final long start = System.currentTimeMillis();
    830         while (!condition.expected().equals(condition.actual())
    831                 && System.currentTimeMillis() - start < timeout) {
    832             sleep(50);
    833         }
    834         assertEquals(description, condition.expected(), condition.actual());
    835     }
    836 
    837     /**
    838      * Performs some work, and waits for the condition to be met.  If the condition is not met in
    839      * each step of the loop, the work is performed again.
    840      *
    841      * @param work The work to perform.
    842      * @param condition The condition.
    843      * @param timeout The timeout.
    844      * @param description Description of the work being performed.
    845      */
    846     void doWorkAndWaitUntilConditionIsTrueOrTimeout(Work work, Condition condition, long timeout,
    847             String description) {
    848         final long start = System.currentTimeMillis();
    849         work.doWork();
    850         while (!condition.expected().equals(condition.actual())
    851                 && System.currentTimeMillis() - start < timeout) {
    852             sleep(50);
    853             work.doWork();
    854         }
    855         assertEquals(description, condition.expected(), condition.actual());
    856     }
    857 
    858     protected interface Condition {
    859         Object expected();
    860         Object actual();
    861     }
    862 
    863     protected interface Work {
    864         void doWork();
    865     }
    866 
    867     /**
    868      * Utility class used to track the number of times a callback was invoked, and the arguments it
    869      * was invoked with. This class is prefixed Invoke rather than the more typical Call for
    870      * disambiguation purposes.
    871      */
    872     protected static final class InvokeCounter {
    873         private final String mName;
    874         private final Object mLock = new Object();
    875         private final ArrayList<Object[]> mInvokeArgs = new ArrayList<>();
    876 
    877         private int mInvokeCount;
    878 
    879         public InvokeCounter(String callbackName) {
    880             mName = callbackName;
    881         }
    882 
    883         public void invoke(Object... args) {
    884             synchronized (mLock) {
    885                 mInvokeCount++;
    886                 mInvokeArgs.add(args);
    887                 mLock.notifyAll();
    888             }
    889         }
    890 
    891         public Object[] getArgs(int index) {
    892             synchronized (mLock) {
    893                 return mInvokeArgs.get(index);
    894             }
    895         }
    896 
    897         public int getInvokeCount() {
    898             synchronized (mLock) {
    899                 return mInvokeCount;
    900             }
    901         }
    902 
    903         public void waitForCount(int count) {
    904             waitForCount(count, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
    905         }
    906 
    907         public void waitForCount(int count, long timeoutMillis) {
    908             waitForCount(count, timeoutMillis, null);
    909         }
    910 
    911         public void waitForCount(int count, long timeoutMillis, String message) {
    912             synchronized (mLock) {
    913                 final long startTimeMillis = SystemClock.uptimeMillis();
    914                 while (mInvokeCount < count) {
    915                     try {
    916                         final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
    917                         final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
    918                         if (remainingTimeMillis <= 0) {
    919                             if (message != null) {
    920                                 fail(message);
    921                             } else {
    922                                 fail(String.format("Expected %s to be called %d times.", mName,
    923                                         count));
    924                             }
    925                         }
    926                         mLock.wait(timeoutMillis);
    927                     } catch (InterruptedException ie) {
    928                         /* ignore */
    929                     }
    930                 }
    931             }
    932         }
    933     }
    934 }
    935