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