1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.telecom.cts;
18 
19 import static android.telecom.cts.TestUtils.PACKAGE;
20 import static android.telecom.cts.TestUtils.TAG;
21 import static android.telecom.cts.TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS;
22 
23 import static org.hamcrest.CoreMatchers.equalTo;
24 import static org.hamcrest.CoreMatchers.not;
25 import static org.junit.Assert.assertNotEquals;
26 import static org.junit.Assert.assertThat;
27 
28 import android.app.UiModeManager;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.res.Configuration;
32 import android.database.ContentObserver;
33 import android.database.Cursor;
34 import android.media.AudioManager;
35 import android.net.Uri;
36 import android.os.Bundle;
37 import android.os.Handler;
38 import android.os.HandlerThread;
39 import android.os.Looper;
40 import android.provider.CallLog;
41 import android.telecom.Call;
42 import android.telecom.CallAudioState;
43 import android.telecom.Conference;
44 import android.telecom.Connection;
45 import android.telecom.ConnectionRequest;
46 import android.telecom.InCallService;
47 import android.telecom.PhoneAccount;
48 import android.telecom.PhoneAccountHandle;
49 import android.telecom.TelecomManager;
50 import android.telecom.VideoProfile;
51 import android.telecom.cts.MockInCallService.InCallServiceCallbacks;
52 import android.telephony.PhoneStateListener;
53 import android.telephony.TelephonyManager;
54 import android.telephony.emergency.EmergencyNumber;
55 import android.test.InstrumentationTestCase;
56 import android.text.TextUtils;
57 import android.util.Log;
58 import android.util.Pair;
59 
60 import com.android.compatibility.common.util.ShellIdentityUtils;
61 
62 import java.util.ArrayList;
63 import java.util.List;
64 import java.util.Objects;
65 import java.util.Random;
66 import java.util.concurrent.CountDownLatch;
67 import java.util.concurrent.Semaphore;
68 import java.util.concurrent.TimeUnit;
69 
70 /**
71  * Base class for Telecom CTS tests that require a {@link CtsConnectionService} and
72  * {@link MockInCallService} to verify Telecom functionality.
73  */
74 public class BaseTelecomTestWithMockServices extends InstrumentationTestCase {
75 
76     public static final int FLAG_REGISTER = 0x1;
77     public static final int FLAG_ENABLE = 0x2;
78     public static final int FLAG_SET_DEFAULT = 0x4;
79 
80     // Don't accidently use emergency number.
81     private static int sCounter = 5553638;
82 
83     public static final String TEST_EMERGENCY_NUMBER = "5553637";
84     public static final Uri TEST_EMERGENCY_URI = Uri.fromParts("tel", TEST_EMERGENCY_NUMBER, null);
85 
86     Context mContext;
87     TelecomManager mTelecomManager;
88     TelephonyManager mTelephonyManager;
89 
90     TestUtils.InvokeCounter mOnBringToForegroundCounter;
91     TestUtils.InvokeCounter mOnCallAudioStateChangedCounter;
92     TestUtils.InvokeCounter mOnPostDialWaitCounter;
93     TestUtils.InvokeCounter mOnCannedTextResponsesLoadedCounter;
94     TestUtils.InvokeCounter mOnSilenceRingerCounter;
95     TestUtils.InvokeCounter mOnConnectionEventCounter;
96     TestUtils.InvokeCounter mOnExtrasChangedCounter;
97     TestUtils.InvokeCounter mOnPropertiesChangedCounter;
98     TestUtils.InvokeCounter mOnRttModeChangedCounter;
99     TestUtils.InvokeCounter mOnRttStatusChangedCounter;
100     TestUtils.InvokeCounter mOnRttInitiationFailedCounter;
101     TestUtils.InvokeCounter mOnRttRequestCounter;
102     TestUtils.InvokeCounter mOnHandoverCompleteCounter;
103     TestUtils.InvokeCounter mOnHandoverFailedCounter;
104     TestUtils.InvokeCounter mOnPhoneAccountChangedCounter;
105     Bundle mPreviousExtras;
106     int mPreviousProperties = -1;
107     PhoneAccountHandle mPreviousPhoneAccountHandle = null;
108 
109     InCallServiceCallbacks mInCallCallbacks;
110     String mPreviousDefaultDialer = null;
111     PhoneAccountHandle mPreviousDefaultOutgoingAccount = null;
112     boolean mShouldRestoreDefaultOutgoingAccount = false;
113     MockConnectionService connectionService = null;
114     boolean mIsEmergencyCallingSetup = false;
115 
116     HandlerThread mPhoneStateListenerThread;
117     Handler mPhoneStateListenerHandler;
118     TestPhoneStateListener mPhoneStateListener;
119     Handler mHandler;
120 
121     static class TestPhoneStateListener extends PhoneStateListener {
122         /** Semaphore released for every callback invocation. */
123         public Semaphore mCallbackSemaphore = new Semaphore(0);
124 
125         List<Pair<Integer, String>> mCallStates = new ArrayList<>();
126         EmergencyNumber mLastOutgoingEmergencyNumber;
127 
128         @Override
onCallStateChanged(int state, String number)129         public void onCallStateChanged(int state, String number) {
130             Log.i(TAG, "onCallStateChanged: state=" + state + ", number=" + number);
131             mCallStates.add(Pair.create(state, number));
132             mCallbackSemaphore.release();
133         }
134 
135         @Override
onOutgoingEmergencyCall(EmergencyNumber emergencyNumber)136         public void onOutgoingEmergencyCall(EmergencyNumber emergencyNumber) {
137             Log.i(TAG, "onOutgoingEmergencyCall: emergencyNumber=" + emergencyNumber);
138             mLastOutgoingEmergencyNumber = emergencyNumber;
139             mCallbackSemaphore.release();
140         }
141     }
142 
143     boolean mShouldTestTelecom = true;
144 
145     @Override
setUp()146     protected void setUp() throws Exception {
147         super.setUp();
148         mContext = getInstrumentation().getContext();
149         mHandler = new Handler(Looper.getMainLooper());
150         mShouldTestTelecom = TestUtils.shouldTestTelecom(mContext);
151         if (!mShouldTestTelecom) {
152             return;
153         }
154 
155         // Assume we start in normal mode at the start of all Telecom tests; a failure to leave car
156         // mode in any of the tests would cause subsequent test failures.
157         assertUiMode(Configuration.UI_MODE_TYPE_NORMAL);
158 
159         mTelecomManager = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
160         mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
161 
162         mPreviousDefaultDialer = TestUtils.getDefaultDialer(getInstrumentation());
163         TestUtils.setDefaultDialer(getInstrumentation(), PACKAGE);
164         setupCallbacks();
165 
166         // PhoneStateListener's public API registers the listener on the calling thread, which must
167         // be a looper thread. So we need to create and register the listener in a custom looper
168         // thread.
169         mPhoneStateListenerThread = new HandlerThread("PhoneStateListenerThread");
170         mPhoneStateListenerThread.start();
171         mPhoneStateListenerHandler = new Handler(mPhoneStateListenerThread.getLooper());
172         final CountDownLatch registeredLatch = new CountDownLatch(1);
173         mPhoneStateListenerHandler.post(new Runnable() {
174             @Override
175             public void run() {
176                 mPhoneStateListener = new TestPhoneStateListener();
177                 ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
178                     (tm) -> tm.listen(mPhoneStateListener,
179                         PhoneStateListener.LISTEN_CALL_STATE | PhoneStateListener
180                             .LISTEN_OUTGOING_EMERGENCY_CALL));
181                 registeredLatch.countDown();
182             }
183         });
184         registeredLatch.await(
185                 TestUtils.WAIT_FOR_PHONE_STATE_LISTENER_REGISTERED_TIMEOUT_S, TimeUnit.SECONDS);
186     }
187 
188     @Override
tearDown()189     protected void tearDown() throws Exception {
190         super.tearDown();
191         if (!mShouldTestTelecom) {
192             return;
193         }
194 
195         final CountDownLatch unregisteredLatch = new CountDownLatch(1);
196         mPhoneStateListenerHandler.post(new Runnable() {
197             @Override
198             public void run() {
199                 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
200                 unregisteredLatch.countDown();
201             }
202         });
203         unregisteredLatch.await(
204                 TestUtils.WAIT_FOR_PHONE_STATE_LISTENER_REGISTERED_TIMEOUT_S, TimeUnit.SECONDS);
205         mPhoneStateListenerThread.quit();
206 
207         cleanupCalls();
208         if (!TextUtils.isEmpty(mPreviousDefaultDialer)) {
209             TestUtils.setDefaultDialer(getInstrumentation(), mPreviousDefaultDialer);
210         }
211         tearDownConnectionService(TestUtils.TEST_PHONE_ACCOUNT_HANDLE);
212         tearDownEmergencyCalling();
213         try {
214             assertMockInCallServiceUnbound();
215         } catch (Throwable t) {
216             // If we haven't unbound, that means there's some dirty state in Telecom that needs
217             // cleaning up. Forcibly unbind and clean up Telecom state so that we don't have a
218             // cascading failure of tests.
219             TestUtils.executeShellCommand(getInstrumentation(), "telecom cleanup-stuck-calls");
220             throw t;
221         }
222     }
223 
setupConnectionService(MockConnectionService connectionService, int flags)224     protected PhoneAccount setupConnectionService(MockConnectionService connectionService,
225             int flags) throws Exception {
226         if (connectionService != null) {
227             this.connectionService = connectionService;
228         } else {
229             // Generate a vanilla mock connection service, if not provided.
230             this.connectionService = new MockConnectionService();
231         }
232         CtsConnectionService.setUp(this.connectionService);
233 
234         if ((flags & FLAG_REGISTER) != 0) {
235             mTelecomManager.registerPhoneAccount(TestUtils.TEST_PHONE_ACCOUNT);
236         }
237         if ((flags & FLAG_ENABLE) != 0) {
238             TestUtils.enablePhoneAccount(getInstrumentation(), TestUtils.TEST_PHONE_ACCOUNT_HANDLE);
239             // Wait till the adb commands have executed and account is enabled in Telecom database.
240             assertPhoneAccountEnabled(TestUtils.TEST_PHONE_ACCOUNT_HANDLE);
241         }
242 
243         if ((flags & FLAG_SET_DEFAULT) != 0) {
244             mPreviousDefaultOutgoingAccount = mTelecomManager.getUserSelectedOutgoingPhoneAccount();
245             mShouldRestoreDefaultOutgoingAccount = true;
246             TestUtils.setDefaultOutgoingPhoneAccount(getInstrumentation(),
247                     TestUtils.TEST_PHONE_ACCOUNT_HANDLE);
248             // Wait till the adb commands have executed and the default has changed.
249             assertPhoneAccountIsDefault(TestUtils.TEST_PHONE_ACCOUNT_HANDLE);
250         }
251 
252         return TestUtils.TEST_PHONE_ACCOUNT;
253     }
254 
tearDownConnectionService(PhoneAccountHandle accountHandle)255     protected void tearDownConnectionService(PhoneAccountHandle accountHandle) throws Exception {
256         if (this.connectionService != null) {
257             assertNumConnections(this.connectionService, 0);
258         }
259         mTelecomManager.unregisterPhoneAccount(accountHandle);
260         CtsConnectionService.tearDown();
261         assertCtsConnectionServiceUnbound();
262         if (mShouldRestoreDefaultOutgoingAccount) {
263             TestUtils.setDefaultOutgoingPhoneAccount(getInstrumentation(),
264                     mPreviousDefaultOutgoingAccount);
265         }
266         this.connectionService = null;
267         mPreviousDefaultOutgoingAccount = null;
268         mShouldRestoreDefaultOutgoingAccount = false;
269     }
270 
setupForEmergencyCalling(String testNumber)271     protected void setupForEmergencyCalling(String testNumber) throws Exception {
272         TestUtils.setSystemDialerOverride(getInstrumentation());
273         TestUtils.addTestEmergencyNumber(getInstrumentation(), testNumber);
274         TestUtils.setTestEmergencyPhoneAccountPackageFilter(getInstrumentation(), mContext);
275         // Emergency calls require special capabilities.
276         TestUtils.registerEmergencyPhoneAccount(getInstrumentation(),
277                 TestUtils.TEST_EMERGENCY_PHONE_ACCOUNT_HANDLE,
278                 TestUtils.ACCOUNT_LABEL + "E", "tel:555-EMER");
279         mIsEmergencyCallingSetup = true;
280     }
281 
tearDownEmergencyCalling()282     protected void tearDownEmergencyCalling() throws Exception {
283         if (!mIsEmergencyCallingSetup) return;
284 
285         TestUtils.clearSystemDialerOverride(getInstrumentation());
286         TestUtils.clearTestEmergencyNumbers(getInstrumentation());
287         TestUtils.clearTestEmergencyPhoneAccountPackageFilter(getInstrumentation());
288         mTelecomManager.unregisterPhoneAccount(TestUtils.TEST_EMERGENCY_PHONE_ACCOUNT_HANDLE);
289     }
290 
startCallTo(Uri address, PhoneAccountHandle accountHandle)291     protected void startCallTo(Uri address, PhoneAccountHandle accountHandle) {
292         final Intent intent = new Intent(Intent.ACTION_CALL, address);
293         if (accountHandle != null) {
294             intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, accountHandle);
295         }
296         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
297         mContext.startActivity(intent);
298     }
299 
sleep(long ms)300     void sleep(long ms) {
301         try {
302             Thread.sleep(ms);
303         } catch (InterruptedException e) {
304         }
305     }
306 
setupCallbacks()307     private void setupCallbacks() {
308         mInCallCallbacks = new InCallServiceCallbacks() {
309             @Override
310             public void onCallAdded(Call call, int numCalls) {
311                 Log.i(TAG, "onCallAdded, Call: " + call + ", Num Calls: " + numCalls);
312                 this.lock.release();
313                 mPreviousPhoneAccountHandle = call.getDetails().getAccountHandle();
314             }
315             @Override
316             public void onCallRemoved(Call call, int numCalls) {
317                 Log.i(TAG, "onCallRemoved, Call: " + call + ", Num Calls: " + numCalls);
318             }
319             @Override
320             public void onParentChanged(Call call, Call parent) {
321                 Log.i(TAG, "onParentChanged, Call: " + call + ", Parent: " + parent);
322                 this.lock.release();
323             }
324             @Override
325             public void onChildrenChanged(Call call, List<Call> children) {
326                 Log.i(TAG, "onChildrenChanged, Call: " + call + "Children: " + children);
327                 this.lock.release();
328             }
329             @Override
330             public void onConferenceableCallsChanged(Call call, List<Call> conferenceableCalls) {
331                 Log.i(TAG, "onConferenceableCallsChanged, Call: " + call + ", Conferenceables: " +
332                         conferenceableCalls);
333             }
334             @Override
335             public void onDetailsChanged(Call call, Call.Details details) {
336                 Log.i(TAG, "onDetailsChanged, Call: " + call + ", Details: " + details);
337                 if (!areBundlesEqual(mPreviousExtras, details.getExtras())) {
338                     mOnExtrasChangedCounter.invoke(call, details);
339                 }
340                 mPreviousExtras = details.getExtras();
341 
342                 if (mPreviousProperties != details.getCallProperties()) {
343                     mOnPropertiesChangedCounter.invoke(call, details);
344                     Log.i(TAG, "onDetailsChanged; properties changed from " + Call.Details.propertiesToString(mPreviousProperties) +
345                             " to " + Call.Details.propertiesToString(details.getCallProperties()));
346                 }
347                 mPreviousProperties = details.getCallProperties();
348 
349                 if (details.getAccountHandle() != null &&
350                         !details.getAccountHandle().equals(mPreviousPhoneAccountHandle)) {
351                     mOnPhoneAccountChangedCounter.invoke(call, details.getAccountHandle());
352                 }
353                 mPreviousPhoneAccountHandle = details.getAccountHandle();
354             }
355             @Override
356             public void onCallDestroyed(Call call) {
357                 Log.i(TAG, "onCallDestroyed, Call: " + call);
358             }
359             @Override
360             public void onCallStateChanged(Call call, int newState) {
361                 Log.i(TAG, "onCallStateChanged, Call: " + call + ", New State: " + newState);
362             }
363             @Override
364             public void onBringToForeground(boolean showDialpad) {
365                 mOnBringToForegroundCounter.invoke(showDialpad);
366             }
367             @Override
368             public void onCallAudioStateChanged(CallAudioState audioState) {
369                 Log.i(TAG, "onCallAudioStateChanged, audioState: " + audioState);
370                 mOnCallAudioStateChangedCounter.invoke(audioState);
371             }
372             @Override
373             public void onPostDialWait(Call call, String remainingPostDialSequence) {
374                 mOnPostDialWaitCounter.invoke(call, remainingPostDialSequence);
375             }
376             @Override
377             public void onCannedTextResponsesLoaded(Call call, List<String> cannedTextResponses) {
378                 mOnCannedTextResponsesLoadedCounter.invoke(call, cannedTextResponses);
379             }
380             @Override
381             public void onConnectionEvent(Call call, String event, Bundle extras) {
382                 mOnConnectionEventCounter.invoke(call, event, extras);
383             }
384 
385             @Override
386             public void onSilenceRinger() {
387                 Log.i(TAG, "onSilenceRinger");
388                 mOnSilenceRingerCounter.invoke();
389             }
390 
391             @Override
392             public void onRttModeChanged(Call call, int mode) {
393                 mOnRttModeChangedCounter.invoke(call, mode);
394             }
395 
396             @Override
397             public void onRttStatusChanged(Call call, boolean enabled, Call.RttCall rttCall) {
398                 mOnRttStatusChangedCounter.invoke(call, enabled, rttCall);
399             }
400 
401             @Override
402             public void onRttRequest(Call call, int id) {
403                 mOnRttRequestCounter.invoke(call, id);
404             }
405 
406             @Override
407             public void onRttInitiationFailure(Call call, int reason) {
408                 mOnRttInitiationFailedCounter.invoke(call, reason);
409             }
410 
411             @Override
412             public void onHandoverComplete(Call call) {
413                 mOnHandoverCompleteCounter.invoke(call);
414             }
415 
416             @Override
417             public void onHandoverFailed(Call call, int reason) {
418                 mOnHandoverFailedCounter.invoke(call, reason);
419             }
420         };
421 
422         MockInCallService.setCallbacks(mInCallCallbacks);
423 
424         // TODO: If more InvokeCounters are added in the future, consider consolidating them into a
425         // single Collection.
426         mOnBringToForegroundCounter = new TestUtils.InvokeCounter("OnBringToForeground");
427         mOnCallAudioStateChangedCounter = new TestUtils.InvokeCounter("OnCallAudioStateChanged");
428         mOnPostDialWaitCounter = new TestUtils.InvokeCounter("OnPostDialWait");
429         mOnCannedTextResponsesLoadedCounter = new TestUtils.InvokeCounter("OnCannedTextResponsesLoaded");
430         mOnSilenceRingerCounter = new TestUtils.InvokeCounter("OnSilenceRinger");
431         mOnConnectionEventCounter = new TestUtils.InvokeCounter("OnConnectionEvent");
432         mOnExtrasChangedCounter = new TestUtils.InvokeCounter("OnDetailsChangedCounter");
433         mOnPropertiesChangedCounter = new TestUtils.InvokeCounter("OnPropertiesChangedCounter");
434         mOnRttModeChangedCounter = new TestUtils.InvokeCounter("mOnRttModeChangedCounter");
435         mOnRttStatusChangedCounter = new TestUtils.InvokeCounter("mOnRttStatusChangedCounter");
436         mOnRttInitiationFailedCounter =
437                 new TestUtils.InvokeCounter("mOnRttInitiationFailedCounter");
438         mOnRttRequestCounter = new TestUtils.InvokeCounter("mOnRttRequestCounter");
439         mOnHandoverCompleteCounter = new TestUtils.InvokeCounter("mOnHandoverCompleteCounter");
440         mOnHandoverFailedCounter = new TestUtils.InvokeCounter("mOnHandoverFailedCounter");
441         mOnPhoneAccountChangedCounter = new TestUtils.InvokeCounter(
442                 "mOnPhoneAccountChangedCounter");
443     }
444 
addAndVerifyNewFailedIncomingCall(Uri incomingHandle, Bundle extras)445     void addAndVerifyNewFailedIncomingCall(Uri incomingHandle, Bundle extras) {
446         assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits());
447         int currentCallCount = 0;
448         if (mInCallCallbacks.getService() != null) {
449             currentCallCount = mInCallCallbacks.getService().getCallCount();
450         }
451 
452         if (extras == null) {
453             extras = new Bundle();
454         }
455         extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, incomingHandle);
456         mTelecomManager.addNewIncomingCall(TestUtils.TEST_PHONE_ACCOUNT_HANDLE, extras);
457 
458         try {
459             if (!connectionService.lock.tryAcquire(TestUtils.WAIT_FOR_CALL_ADDED_TIMEOUT_S,
460                     TimeUnit.SECONDS)) {
461                 fail("Incoming Connection failure indication did not get called.");
462             }
463         } catch (InterruptedException e) {
464             fail("InterruptedException while waiting for incoming call failure");
465         }
466 
467         assertEquals("ConnectionService did not receive failed connection",
468                 1, connectionService.failedConnections.size());
469 
470         assertEquals("Address is not correct for failed connection",
471                 connectionService.failedConnections.get(0).getAddress(), incomingHandle);
472 
473         assertEquals("InCallService should contain the same number of calls.",
474                 currentCallCount,
475                 mInCallCallbacks.getService().getCallCount());
476     }
477 
478     /**
479      * Puts Telecom in a state where there is an incoming call provided by the
480      * {@link CtsConnectionService} which can be tested.
481      */
addAndVerifyNewIncomingCall(Uri incomingHandle, Bundle extras)482     void addAndVerifyNewIncomingCall(Uri incomingHandle, Bundle extras) {
483         assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits());
484         int currentCallCount = 0;
485         if (mInCallCallbacks.getService() != null) {
486             currentCallCount = mInCallCallbacks.getService().getCallCount();
487         }
488 
489         if (extras == null) {
490             extras = new Bundle();
491         }
492         extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, incomingHandle);
493         mTelecomManager.addNewIncomingCall(TestUtils.TEST_PHONE_ACCOUNT_HANDLE, extras);
494 
495         try {
496             if (!mInCallCallbacks.lock.tryAcquire(TestUtils.WAIT_FOR_CALL_ADDED_TIMEOUT_S,
497                         TimeUnit.SECONDS)) {
498                 fail("No call added to InCallService.");
499             }
500         } catch (InterruptedException e) {
501             Log.i(TAG, "Test interrupted!");
502         }
503 
504         assertEquals("InCallService should contain 1 more call after adding a call.",
505                 currentCallCount + 1,
506                 mInCallCallbacks.getService().getCallCount());
507     }
508 
509     /**
510      *  Puts Telecom in a state where there is an active call provided by the
511      *  {@link CtsConnectionService} which can be tested.
512      */
placeAndVerifyCall()513     void placeAndVerifyCall() {
514         placeAndVerifyCall(null);
515     }
516 
placeAndVerifyCallByRedirection(boolean wasCancelled)517     void placeAndVerifyCallByRedirection(boolean wasCancelled) {
518         placeAndVerifyCallByRedirection(null, wasCancelled);
519     }
520 
521     /**
522      *  Puts Telecom in a state where there is an active call provided by the
523      *  {@link CtsConnectionService} which can be tested.
524      */
placeAndVerifyCallByRedirection(Bundle extras, boolean wasCancelled)525     void placeAndVerifyCallByRedirection(Bundle extras, boolean wasCancelled) {
526         int currentCallCount = (getInCallService() == null) ? 0 : getInCallService().getCallCount();
527         int currentConnections = getNumberOfConnections();
528         // We expect a new connection if it wasn't cancelled.
529         if (!wasCancelled) {
530             currentConnections++;
531             currentCallCount++;
532         }
533         placeAndVerifyCall(extras, VideoProfile.STATE_AUDIO_ONLY, currentConnections,
534                 currentCallCount);
535         // Ensure the new outgoing call broadcast fired for the outgoing call.
536         assertOutgoingCallBroadcastReceived(true);
537 
538         // CTS test does not have read call log permission so should not get the phone number.
539         assertNull(NewOutgoingCallBroadcastReceiver.getReceivedNumber());
540     }
541 
542     /**
543      *  Puts Telecom in a state where there is an active call provided by the
544      *  {@link CtsConnectionService} which can be tested.
545      *
546      *  @param videoState the video state of the call.
547      */
placeAndVerifyCall(int videoState)548     void placeAndVerifyCall(int videoState) {
549         placeAndVerifyCall(null, videoState);
550     }
551 
552     /**
553      *  Puts Telecom in a state where there is an active call provided by the
554      *  {@link CtsConnectionService} which can be tested.
555      */
placeAndVerifyCall(Bundle extras)556     void placeAndVerifyCall(Bundle extras) {
557         placeAndVerifyCall(extras, VideoProfile.STATE_AUDIO_ONLY);
558     }
559 
560     /**
561      *  Puts Telecom in a state where there is an active call provided by the
562      *  {@link CtsConnectionService} which can be tested.
563      */
placeAndVerifyCall(Bundle extras, int videoState)564     void placeAndVerifyCall(Bundle extras, int videoState) {
565         int currentCallCount = (getInCallService() == null) ? 0 : getInCallService().getCallCount();
566         // We expect placing the call adds a new call/connection.
567         placeAndVerifyCall(extras, videoState, getNumberOfConnections() + 1, currentCallCount + 1);
568         assertOutgoingCallBroadcastReceived(true);
569 
570         // CTS test does not have read call log permission so should not get the phone number.
571         assertNull(NewOutgoingCallBroadcastReceiver.getReceivedNumber());
572     }
573 
574     /**
575      *  Puts Telecom in a state where there is an active call provided by the
576      *  {@link CtsConnectionService} which can be tested.
577      */
placeAndVerifyCall(Bundle extras, int videoState, int expectedConnectionCount, int expectedCallCount)578     void placeAndVerifyCall(Bundle extras, int videoState, int expectedConnectionCount,
579             int expectedCallCount) {
580         assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits());
581         placeNewCallWithPhoneAccount(extras, videoState);
582 
583         try {
584             if (!mInCallCallbacks.lock.tryAcquire(TestUtils.WAIT_FOR_CALL_ADDED_TIMEOUT_S,
585                         TimeUnit.SECONDS)) {
586                 fail("No call added to InCallService.");
587             }
588         } catch (InterruptedException e) {
589             Log.i(TAG, "Test interrupted!");
590         }
591 
592         // Make sure any procedures to disconnect existing calls (makeRoomForOutgoingCall)
593         // complete successfully
594         TestUtils.waitOnLocalMainLooper(WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
595         TestUtils.waitOnAllHandlers(getInstrumentation());
596 
597         assertEquals("InCallService should match the expected count.", expectedCallCount,
598                 mInCallCallbacks.getService().getCallCount());
599 
600         // The connectionService.lock is released in
601         // MockConnectionService#onCreateOutgoingConnection, however the connection will not
602         // actually be added to the list of connections in the ConnectionService until shortly
603         // afterwards.  So there is still a potential for the lock to be released before it would
604         // be seen by calls to ConnectionService#getAllConnections().
605         // We will wait here until the list of connections includes one more connection to ensure
606         // that placing the call has fully completed.
607         assertCSConnections(expectedConnectionCount);
608     }
609 
610     /**
611      * Place an emergency call and verify that it has been setup properly.
612      *
613      * @param supportsHold If telecom supports holding emergency calls, this will expect two
614      * calls. If telecom does not support holding emergency calls, this will expect only the
615      * emergency call to be active.
616      * @return The emergency connection
617      */
placeAndVerifyEmergencyCall(boolean supportsHold)618     public Connection placeAndVerifyEmergencyCall(boolean supportsHold) {
619         Bundle extras = new Bundle();
620         extras.putParcelable(TestUtils.EXTRA_PHONE_NUMBER, TEST_EMERGENCY_URI);
621         int currentConnectionCount = supportsHold ?
622                 getNumberOfConnections() + 1 : getNumberOfConnections();
623         int currentCallCount = (getInCallService() == null) ? 0 : getInCallService().getCallCount();
624         currentCallCount = supportsHold ? currentCallCount + 1 : currentCallCount;
625         // The device only supports a max of two calls active at any one time
626         currentCallCount = Math.min(currentCallCount, 2);
627         placeAndVerifyCall(extras, VideoProfile.STATE_AUDIO_ONLY, currentConnectionCount,
628                 currentCallCount);
629         assertOutgoingCallBroadcastReceived(true);
630         Connection connection = verifyConnectionForOutgoingCall(TEST_EMERGENCY_URI);
631         TestUtils.waitOnAllHandlers(getInstrumentation());
632         return connection;
633     }
634 
getNumberOfConnections()635     int getNumberOfConnections() {
636         return CtsConnectionService.getAllConnectionsFromTelecom().size();
637     }
638 
getConnection(Uri address)639     Connection getConnection(Uri address) {
640         return CtsConnectionService.getAllConnectionsFromTelecom().stream()
641                 .filter(c -> c.getAddress().equals(address)).findFirst().orElse(null);
642     }
643 
verifyConnectionForOutgoingCall()644     MockConnection verifyConnectionForOutgoingCall() {
645         // Assuming only 1 connection present
646         return verifyConnectionForOutgoingCall(0);
647     }
648 
verifyConnectionForOutgoingCall(int connectionIndex)649     MockConnection verifyConnectionForOutgoingCall(int connectionIndex) {
650         try {
651             if (!connectionService.lock.tryAcquire(TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
652                     TimeUnit.MILLISECONDS)) {
653                 fail("No outgoing call connection requested by Telecom");
654             }
655         } catch (InterruptedException e) {
656             Log.i(TAG, "Test interrupted!");
657         }
658 
659         assertThat("Telecom should create outgoing connection for outgoing call",
660                 connectionService.outgoingConnections.size(), not(equalTo(0)));
661         MockConnection connection = connectionService.outgoingConnections.get(connectionIndex);
662         return connection;
663     }
664 
verifyConnectionForOutgoingCall(Uri address)665     MockConnection verifyConnectionForOutgoingCall(Uri address) {
666         try {
667             if (!connectionService.lock.tryAcquire(TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
668                     TimeUnit.MILLISECONDS)) {
669                 fail("No outgoing call connection requested by Telecom");
670             }
671         } catch (InterruptedException e) {
672             Log.i(TAG, "Test interrupted!");
673         }
674 
675         assertThat("Telecom should create outgoing connection for outgoing call",
676                 connectionService.outgoingConnections.size(), not(equalTo(0)));
677         Connection connection = getConnection(address);
678         assertNotNull("Could not find outgoing connection in list of active connections.",
679                 connection);
680         if (connection instanceof MockConnection) {
681             if (connectionService.outgoingConnections.contains(connection)) {
682                 return (MockConnection) connection;
683             }
684         }
685         return null;
686     }
687 
verifyConnectionForIncomingCall()688     MockConnection verifyConnectionForIncomingCall() {
689         // Assuming only 1 connection present
690         return verifyConnectionForIncomingCall(0);
691     }
692 
verifyConnectionForIncomingCall(int connectionIndex)693     MockConnection verifyConnectionForIncomingCall(int connectionIndex) {
694         try {
695             if (!connectionService.lock.tryAcquire(TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
696                     TimeUnit.MILLISECONDS)) {
697                 fail("No outgoing call connection requested by Telecom");
698             }
699         } catch (InterruptedException e) {
700             Log.i(TAG, "Test interrupted!");
701         }
702 
703         assertThat("Telecom should create incoming connections for incoming calls",
704                 connectionService.incomingConnections.size(), not(equalTo(0)));
705         MockConnection connection = connectionService.incomingConnections.get(connectionIndex);
706         setAndVerifyConnectionForIncomingCall(connection);
707         return connection;
708     }
709 
setAndVerifyConnectionForIncomingCall(MockConnection connection)710     void setAndVerifyConnectionForIncomingCall(MockConnection connection) {
711         if (connection.getState() == Connection.STATE_ACTIVE) {
712             // If the connection is already active (like if it got picked up immediately), don't
713             // bother with setting it back to ringing.
714             return;
715         }
716         connection.setRinging();
717         assertConnectionState(connection, Connection.STATE_RINGING);
718     }
719 
setAndVerifyConferenceablesForOutgoingConnection(int connectionIndex)720     void setAndVerifyConferenceablesForOutgoingConnection(int connectionIndex) {
721         assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits());
722         // Make all other outgoing connections as conferenceable with this connection.
723         MockConnection connection = connectionService.outgoingConnections.get(connectionIndex);
724         List<Connection> confConnections =
725                 new ArrayList<>(connectionService.outgoingConnections.size());
726         for (Connection c : connectionService.outgoingConnections) {
727             if (c != connection) {
728                 confConnections.add(c);
729             }
730         }
731         connection.setConferenceableConnections(confConnections);
732         assertEquals(connection.getConferenceables(), confConnections);
733     }
734 
addConferenceCall(Call call1, Call call2)735     void addConferenceCall(Call call1, Call call2) {
736         assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits());
737         int currentConfCallCount = 0;
738         if (mInCallCallbacks.getService() != null) {
739             currentConfCallCount = mInCallCallbacks.getService().getConferenceCallCount();
740         }
741         // Verify that the calls have each other on their conferenceable list before proceeding
742         List<Call> callConfList = new ArrayList<>();
743         callConfList.add(call2);
744         assertCallConferenceableList(call1, callConfList);
745 
746         callConfList.clear();
747         callConfList.add(call1);
748         assertCallConferenceableList(call2, callConfList);
749 
750         call1.conference(call2);
751 
752         /**
753          * We should have 1 onCallAdded, 2 onChildrenChanged and 2 onParentChanged invoked, so
754          * we should have 5 available permits on the incallService lock.
755          */
756         try {
757             if (!mInCallCallbacks.lock.tryAcquire(5, 3, TimeUnit.SECONDS)) {
758                 fail("Conference addition failed.");
759             }
760         } catch (InterruptedException e) {
761             Log.i(TAG, "Test interrupted!");
762         }
763 
764         assertEquals("InCallService should contain 1 more call after adding a conf call.",
765                 currentConfCallCount + 1,
766                 mInCallCallbacks.getService().getConferenceCallCount());
767     }
768 
splitFromConferenceCall(Call call1)769     void splitFromConferenceCall(Call call1) {
770         assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits());
771 
772         call1.splitFromConference();
773         /**
774          * We should have 1 onChildrenChanged and 1 onParentChanged invoked, so
775          * we should have 2 available permits on the incallService lock.
776          */
777         try {
778             if (!mInCallCallbacks.lock.tryAcquire(2, 3, TimeUnit.SECONDS)) {
779                 fail("Conference split failed");
780             }
781         } catch (InterruptedException e) {
782             Log.i(TAG, "Test interrupted!");
783         }
784     }
785 
verifyConferenceForOutgoingCall()786     MockConference verifyConferenceForOutgoingCall() {
787         try {
788             if (!connectionService.lock.tryAcquire(TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
789                     TimeUnit.MILLISECONDS)) {
790                 fail("No outgoing conference requested by Telecom");
791             }
792         } catch (InterruptedException e) {
793             Log.i(TAG, "Test interrupted!");
794         }
795         // Return the newly created conference object to the caller
796         MockConference conference = connectionService.conferences.get(0);
797         setAndVerifyConferenceForOutgoingCall(conference);
798         return conference;
799     }
800 
verifyAdhocConferenceCall()801     Pair<Conference, ConnectionRequest> verifyAdhocConferenceCall() {
802         try {
803             if (!connectionService.lock.tryAcquire(2, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
804                     TimeUnit.MILLISECONDS)) {
805                 fail("No conference requested by Telecom");
806             }
807         } catch (InterruptedException e) {
808             Log.i(TAG, "Test interrupted!");
809         }
810         return new Pair<>(connectionService.conferences.get(0),
811                 connectionService.connectionRequest);
812     }
813 
setAndVerifyConferenceForOutgoingCall(MockConference conference)814     void setAndVerifyConferenceForOutgoingCall(MockConference conference) {
815         conference.setActive();
816         assertConferenceState(conference, Connection.STATE_ACTIVE);
817     }
818 
verifyPhoneStateListenerCallbacksForCall(int expectedCallState, String expectedNumber)819     void verifyPhoneStateListenerCallbacksForCall(int expectedCallState, String expectedNumber)
820             throws Exception {
821         assertTrue(mPhoneStateListener.mCallbackSemaphore.tryAcquire(
822                 TestUtils.WAIT_FOR_PHONE_STATE_LISTENER_CALLBACK_TIMEOUT_S, TimeUnit.SECONDS));
823         // At this point we can only be sure that we got AN update, but not necessarily the one we
824         // are looking for; wait until we see the state we want before verifying further.
825         waitUntilConditionIsTrueOrTimeout(new Condition() {
826                                               @Override
827                                               public Object expected() {
828                                                   return true;
829                                               }
830 
831                                               @Override
832                                               public Object actual() {
833                                                   return mPhoneStateListener.mCallStates
834                                                           .stream()
835                                                           .filter(p -> p.first.equals(
836                                                                   expectedCallState)
837                                                                   && p.second.equals(
838                                                                   expectedNumber))
839                                                           .count() > 0;
840                                               }
841                                           },
842                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
843                 "Expected call state " + expectedCallState + " and number "
844                         + expectedNumber);
845 
846 
847         // Get the most recent callback; it is possible that there was an initial state reported due
848         // to the fact that TelephonyManager will sometimes give an initial state back to the caller
849         // when the listener is registered.
850         Pair<Integer, String> callState = mPhoneStateListener.mCallStates.get(
851                 mPhoneStateListener.mCallStates.size() - 1);
852         assertEquals(expectedCallState, (int) callState.first);
853         // Note: We do NOT check the phone number here.  Due to changes in how the phone state
854         // broadcast is sent, the caller may receive multiple broadcasts, and the number will be
855         // present in one or the other.  We waited for a full matching broadcast above so we can
856         // be sure the number was reported as expected.
857     }
858 
verifyPhoneStateListenerCallbacksForEmergencyCall(String expectedNumber)859     void verifyPhoneStateListenerCallbacksForEmergencyCall(String expectedNumber)
860         throws Exception {
861         assertTrue(mPhoneStateListener.mCallbackSemaphore.tryAcquire(
862             TestUtils.WAIT_FOR_PHONE_STATE_LISTENER_CALLBACK_TIMEOUT_S, TimeUnit.SECONDS));
863         // At this point we can only be sure that we got AN update, but not necessarily the one we
864         // are looking for; wait until we see the state we want before verifying further.
865         waitUntilConditionIsTrueOrTimeout(new Condition() {
866                                               @Override
867                                               public Object expected() {
868                                                   return true;
869                                               }
870 
871                                               @Override
872                                               public Object actual() {
873                                                   return mPhoneStateListener
874                                                       .mLastOutgoingEmergencyNumber != null
875                                                       && mPhoneStateListener
876                                                       .mLastOutgoingEmergencyNumber.getNumber()
877                                                       .equals(expectedNumber);
878                                               }
879                                           },
880             WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
881             "Expected emergency number: " + expectedNumber);
882 
883         assertEquals(mPhoneStateListener.mLastOutgoingEmergencyNumber.getNumber(),
884             expectedNumber);
885     }
886 
887     /**
888      * Disconnect the created test call and verify that Telecom has cleared all calls.
889      */
cleanupCalls()890     void cleanupCalls() {
891         if (mInCallCallbacks != null && mInCallCallbacks.getService() != null) {
892             mInCallCallbacks.getService().disconnectAllConferenceCalls();
893             mInCallCallbacks.getService().disconnectAllCalls();
894             assertNumConferenceCalls(mInCallCallbacks.getService(), 0);
895             assertNumCalls(mInCallCallbacks.getService(), 0);
896         }
897     }
898 
899     /**
900      * Place a new outgoing call via the {@link CtsConnectionService}
901      */
placeNewCallWithPhoneAccount(Bundle extras, int videoState)902     private void placeNewCallWithPhoneAccount(Bundle extras, int videoState) {
903         if (extras == null) {
904             extras = new Bundle();
905         }
906         if (!extras.containsKey(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE)) {
907             extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
908                     TestUtils.TEST_PHONE_ACCOUNT_HANDLE);
909         }
910 
911         if (!VideoProfile.isAudioOnly(videoState)) {
912             extras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState);
913         }
914         Uri number;
915         if (extras.containsKey(TestUtils.EXTRA_PHONE_NUMBER)) {
916             number = extras.getParcelable(TestUtils.EXTRA_PHONE_NUMBER);
917         } else {
918             number = createTestNumber();
919         }
920         mTelecomManager.placeCall(number, extras);
921     }
922 
923     /**
924      * Create a new number each time for a new test. Telecom has special logic to reuse certain
925      * calls if multiple calls to the same number are placed within a short period of time which
926      * can cause certain tests to fail.
927      */
createTestNumber()928     Uri createTestNumber() {
929         return Uri.fromParts("tel", String.valueOf(++sCounter), null);
930     }
931 
932     /**
933      * Creates a new random phone number in the range:
934      * 000-000-0000
935      * to
936      * 999-999-9999
937      * @return Randomized phone number.
938      */
createRandomTestNumber()939     Uri createRandomTestNumber() {
940         return Uri.fromParts("tel", String.format("16%05d", new Random().nextInt(99999))
941                 + String.format("%04d", new Random().nextInt(9999)), null);
942     }
943 
getTestNumber()944     public static Uri getTestNumber() {
945         return Uri.fromParts("tel", String.valueOf(sCounter), null);
946     }
947 
isLoggedCall(PhoneAccountHandle handle)948     public boolean isLoggedCall(PhoneAccountHandle handle) {
949         PhoneAccount phoneAccount = mTelecomManager.getPhoneAccount(handle);
950         Bundle extras = phoneAccount.getExtras();
951         if (extras == null) {
952             extras = new Bundle();
953         }
954         boolean isSelfManaged = (phoneAccount.getCapabilities()
955                 & PhoneAccount.CAPABILITY_SELF_MANAGED) == PhoneAccount.CAPABILITY_SELF_MANAGED;
956         // Calls are logged if:
957         // 1. They're not self-managed
958         // 2. They're self-managed and are configured to request logging.
959         return (!isSelfManaged
960                 || (isSelfManaged
961                 && extras.getBoolean(PhoneAccount.EXTRA_LOG_SELF_MANAGED_CALLS)
962                 && (phoneAccount.getSupportedUriSchemes().contains(PhoneAccount.SCHEME_TEL)
963                 || phoneAccount.getSupportedUriSchemes().contains(PhoneAccount.SCHEME_SIP))));
964     }
965 
getCallLogEntryLatch()966     public CountDownLatch getCallLogEntryLatch() {
967         CountDownLatch changeLatch = new CountDownLatch(1);
968         mContext.getContentResolver().registerContentObserver(
969                 CallLog.Calls.CONTENT_URI, true,
970                 new ContentObserver(mHandler) {
971                     @Override
972                     public void onChange(boolean selfChange, Uri uri) {
973                         mContext.getContentResolver().unregisterContentObserver(this);
974                         changeLatch.countDown();
975                         super.onChange(selfChange);
976                     }
977                 });
978         return changeLatch;
979     }
980 
981 
verifyCallLogging(CountDownLatch logLatch, boolean isCallLogged, Uri testNumber)982     public void verifyCallLogging(CountDownLatch logLatch, boolean isCallLogged, Uri testNumber) {
983         Cursor logCursor = getLatestCallLogCursorIfMatchesUri(logLatch, isCallLogged, testNumber);
984         if (isCallLogged) {
985             assertNotNull("Call log entry not found for test number", logCursor);
986         }
987     }
988 
verifyCallLogging(Uri testNumber, int expectedLogType)989     public void verifyCallLogging(Uri testNumber, int expectedLogType) {
990         CountDownLatch logLatch = getCallLogEntryLatch();
991         Cursor logCursor = getLatestCallLogCursorIfMatchesUri(logLatch, true /*isCallLogged*/,
992                 testNumber);
993         assertNotNull("Call log entry not found for test number", logCursor);
994         int typeIndex = logCursor.getColumnIndex(CallLog.Calls.TYPE);
995         int type = logCursor.getInt(typeIndex);
996         assertEquals("recorded type does not match expected", expectedLogType, type);
997     }
998 
getLatestCallLogCursorIfMatchesUri(CountDownLatch latch, boolean newLogExpected, Uri testNumber)999     public Cursor getLatestCallLogCursorIfMatchesUri(CountDownLatch latch, boolean newLogExpected,
1000             Uri testNumber) {
1001         if (newLogExpected) {
1002             // Wait for the content observer to report that we have gotten a new call log entry.
1003             try {
1004                 latch.await(WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
1005             } catch (InterruptedException ie) {
1006                 fail("Expected log latch");
1007             }
1008         }
1009 
1010         // Query the latest entry into the call log.
1011         Cursor callsCursor = mContext.getContentResolver().query(CallLog.Calls.CONTENT_URI, null,
1012                 null, null, CallLog.Calls._ID + " DESC limit 1;");
1013         int numberIndex = callsCursor.getColumnIndex(CallLog.Calls.NUMBER);
1014         if (callsCursor.moveToNext()) {
1015             String number = callsCursor.getString(numberIndex);
1016             if (testNumber.getSchemeSpecificPart().equals(number)) {
1017                 return callsCursor;
1018             } else {
1019                 // Last call log entry doesnt match expected number.
1020                 return null;
1021             }
1022         }
1023         // No Calls
1024         return null;
1025     }
1026 
assertNumCalls(final MockInCallService inCallService, final int numCalls)1027     void assertNumCalls(final MockInCallService inCallService, final int numCalls) {
1028         waitUntilConditionIsTrueOrTimeout(new Condition() {
1029             @Override
1030             public Object expected() {
1031                 return numCalls;
1032             }
1033             @Override
1034             public Object actual() {
1035                 return inCallService.getCallCount();
1036             }
1037         },
1038         WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1039         "InCallService should contain " + numCalls + " calls."
1040     );
1041     }
1042 
assertNumConferenceCalls(final MockInCallService inCallService, final int numCalls)1043     void assertNumConferenceCalls(final MockInCallService inCallService, final int numCalls) {
1044         waitUntilConditionIsTrueOrTimeout(new Condition() {
1045             @Override
1046             public Object expected() {
1047                 return numCalls;
1048             }
1049             @Override
1050             public Object actual() {
1051                 return inCallService.getConferenceCallCount();
1052             }
1053         },
1054         WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1055         "InCallService should contain " + numCalls + " conference calls."
1056     );
1057     }
1058 
assertCSConnections(final int numConnections)1059     void assertCSConnections(final int numConnections) {
1060         waitUntilConditionIsTrueOrTimeout(new Condition() {
1061                                               @Override
1062                                               public Object expected() {
1063                                                   return numConnections;
1064                                               }
1065 
1066                                               @Override
1067                                               public Object actual() {
1068                                                   return CtsConnectionService
1069                                                           .getAllConnectionsFromTelecom()
1070                                                           .size();
1071                                               }
1072                                           },
1073                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1074                 "ConnectionService should contain " + numConnections + " connections."
1075         );
1076     }
1077 
assertNumConnections(final MockConnectionService connService, final int numConnections)1078     void assertNumConnections(final MockConnectionService connService, final int numConnections) {
1079         waitUntilConditionIsTrueOrTimeout(new Condition() {
1080                                               @Override
1081                                               public Object expected() {
1082                                                   return numConnections;
1083                                               }
1084                                               @Override
1085                                               public Object actual() {
1086                                                   return connService.getAllConnections().size();
1087                                               }
1088                                           },
1089                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1090                 "ConnectionService should contain " + numConnections + " connections."
1091         );
1092     }
1093 
assertMuteState(final InCallService incallService, final boolean isMuted)1094     void assertMuteState(final InCallService incallService, final boolean isMuted) {
1095         waitUntilConditionIsTrueOrTimeout(
1096                 new Condition() {
1097                     @Override
1098                     public Object expected() {
1099                         return isMuted;
1100                     }
1101 
1102                     @Override
1103                     public Object actual() {
1104                         final CallAudioState state = incallService.getCallAudioState();
1105                         return state == null ? null : state.isMuted();
1106                     }
1107                 },
1108                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1109                 "Phone's mute state should be: " + isMuted
1110         );
1111     }
1112 
assertMuteState(final MockConnection connection, final boolean isMuted)1113     void assertMuteState(final MockConnection connection, final boolean isMuted) {
1114         waitUntilConditionIsTrueOrTimeout(
1115                 new Condition() {
1116                     @Override
1117                     public Object expected() {
1118                         return isMuted;
1119                     }
1120 
1121                     @Override
1122                     public Object actual() {
1123                         final CallAudioState state = connection.getCallAudioState();
1124                         return state == null ? null : state.isMuted();
1125                     }
1126                 },
1127                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1128                 "Connection's mute state should be: " + isMuted
1129         );
1130     }
1131 
assertAudioRoute(final InCallService incallService, final int route)1132     void assertAudioRoute(final InCallService incallService, final int route) {
1133         waitUntilConditionIsTrueOrTimeout(
1134                 new Condition() {
1135                     @Override
1136                     public Object expected() {
1137                         return route;
1138                     }
1139 
1140                     @Override
1141                     public Object actual() {
1142                         final CallAudioState state = incallService.getCallAudioState();
1143                         return state == null ? null : state.getRoute();
1144                     }
1145                 },
1146                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1147                 "Phone's audio route should be: " + route
1148         );
1149     }
1150 
assertNotAudioRoute(final InCallService incallService, final int route)1151     void assertNotAudioRoute(final InCallService incallService, final int route) {
1152         waitUntilConditionIsTrueOrTimeout(
1153                 new Condition() {
1154                     @Override
1155                     public Object expected() {
1156                         return new Boolean(true);
1157                     }
1158 
1159                     @Override
1160                     public Object actual() {
1161                         final CallAudioState state = incallService.getCallAudioState();
1162                         return route != state.getRoute();
1163                     }
1164                 },
1165                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1166                 "Phone's audio route should not be: " + route
1167         );
1168     }
1169 
assertAudioRoute(final MockConnection connection, final int route)1170     void assertAudioRoute(final MockConnection connection, final int route) {
1171         waitUntilConditionIsTrueOrTimeout(
1172                 new Condition() {
1173                     @Override
1174                     public Object expected() {
1175                         return route;
1176                     }
1177 
1178                     @Override
1179                     public Object actual() {
1180                         final CallAudioState state = ((Connection) connection).getCallAudioState();
1181                         return state == null ? null : state.getRoute();
1182                     }
1183                 },
1184                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1185                 "Connection's audio route should be: " + route
1186         );
1187     }
1188 
assertConnectionState(final Connection connection, final int state)1189     void assertConnectionState(final Connection connection, final int state) {
1190         waitUntilConditionIsTrueOrTimeout(
1191                 new Condition() {
1192                     @Override
1193                     public Object expected() {
1194                         return state;
1195                     }
1196 
1197                     @Override
1198                     public Object actual() {
1199                         return connection.getState();
1200                     }
1201                 },
1202                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1203                 "Connection should be in state " + state
1204         );
1205     }
1206 
assertCallState(final Call call, final int state)1207     void assertCallState(final Call call, final int state) {
1208         waitUntilConditionIsTrueOrTimeout(
1209                 new Condition() {
1210                     @Override
1211                     public Object expected() {
1212                         return state;
1213                     }
1214 
1215                     @Override
1216                     public Object actual() {
1217                         return call.getState();
1218                     }
1219                 },
1220                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1221                 "Call: " + call + " should be in state " + state
1222         );
1223     }
1224 
assertCallConferenceableList(final Call call, final List<Call> conferenceableList)1225     void assertCallConferenceableList(final Call call, final List<Call> conferenceableList) {
1226         waitUntilConditionIsTrueOrTimeout(
1227                 new Condition() {
1228                     @Override
1229                     public Object expected() {
1230                         return conferenceableList;
1231                     }
1232 
1233                     @Override
1234                     public Object actual() {
1235                         return call.getConferenceableCalls();
1236                     }
1237                 },
1238                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1239                 "Call: " + call + " does not have the correct conferenceable call list."
1240         );
1241     }
1242 
assertDtmfString(final MockConnection connection, final String dtmfString)1243     void assertDtmfString(final MockConnection connection, final String dtmfString) {
1244         waitUntilConditionIsTrueOrTimeout(new Condition() {
1245                 @Override
1246                 public Object expected() {
1247                     return dtmfString;
1248                 }
1249 
1250                 @Override
1251                 public Object actual() {
1252                     return connection.getDtmfString();
1253                 }
1254             },
1255             WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1256             "DTMF string should be equivalent to entered DTMF characters: " + dtmfString
1257         );
1258     }
1259 
assertDtmfString(final MockConference conference, final String dtmfString)1260     void assertDtmfString(final MockConference conference, final String dtmfString) {
1261         waitUntilConditionIsTrueOrTimeout(new Condition() {
1262                 @Override
1263                 public Object expected() {
1264                     return dtmfString;
1265                 }
1266 
1267                 @Override
1268                 public Object actual() {
1269                     return conference.getDtmfString();
1270                 }
1271             },
1272             WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1273             "DTMF string should be equivalent to entered DTMF characters: " + dtmfString
1274         );
1275     }
1276 
assertCallDisplayName(final Call call, final String name)1277     void assertCallDisplayName(final Call call, final String name) {
1278         waitUntilConditionIsTrueOrTimeout(
1279                 new Condition() {
1280                     @Override
1281                     public Object expected() {
1282                         return name;
1283                     }
1284 
1285                     @Override
1286                     public Object actual() {
1287                         return call.getDetails().getCallerDisplayName();
1288                     }
1289                 },
1290                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1291                 "Call should have display name: " + name
1292         );
1293     }
1294 
assertCallConnectTimeChanged(final Call call, final long time)1295     void assertCallConnectTimeChanged(final Call call, final long time) {
1296         waitUntilConditionIsTrueOrTimeout(
1297                 new Condition() {
1298                     @Override
1299                     public Object expected() {
1300                         return true;
1301                     }
1302 
1303                     @Override
1304                     public Object actual() {
1305                         return call.getDetails().getConnectTimeMillis() != time;
1306                     }
1307                 },
1308                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1309                 "Call have connect time: " + time
1310         );
1311     }
1312 
assertConnectionCallDisplayName(final Connection connection, final String name)1313     void assertConnectionCallDisplayName(final Connection connection, final String name) {
1314         waitUntilConditionIsTrueOrTimeout(
1315                 new Condition() {
1316                     @Override
1317                     public Object expected() {
1318                         return name;
1319                     }
1320 
1321                     @Override
1322                     public Object actual() {
1323                         return connection.getCallerDisplayName();
1324                     }
1325                 },
1326                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1327                 "Connection should have display name: " + name
1328         );
1329     }
1330 
assertDisconnectReason(final Connection connection, final String disconnectReason)1331     void assertDisconnectReason(final Connection connection, final String disconnectReason) {
1332         waitUntilConditionIsTrueOrTimeout(
1333                 new Condition() {
1334                     @Override
1335                     public Object expected() {
1336                         return disconnectReason;
1337                     }
1338 
1339                     @Override
1340                     public Object actual() {
1341                         return connection.getDisconnectCause().getReason();
1342                     }
1343                 },
1344                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1345                 "Connection should have been disconnected with reason: " + disconnectReason
1346         );
1347     }
1348 
assertConferenceState(final Conference conference, final int state)1349     void assertConferenceState(final Conference conference, final int state) {
1350         waitUntilConditionIsTrueOrTimeout(
1351                 new Condition() {
1352                     @Override
1353                     public Object expected() {
1354                         return state;
1355                     }
1356 
1357                     @Override
1358                     public Object actual() {
1359                         return conference.getState();
1360                     }
1361                 },
1362                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1363                 "Conference should be in state " + state
1364         );
1365     }
1366 
1367 
assertOutgoingCallBroadcastReceived(boolean received)1368     void assertOutgoingCallBroadcastReceived(boolean received) {
1369         waitUntilConditionIsTrueOrTimeout(
1370                 new Condition() {
1371                     @Override
1372                     public Object expected() {
1373                         return received;
1374                     }
1375 
1376                     @Override
1377                     public Object actual() {
1378                         return NewOutgoingCallBroadcastReceiver
1379                                 .isNewOutgoingCallBroadcastReceived();
1380                     }
1381                 },
1382                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1383                 received ? "Outgoing Call Broadcast should be received"
1384                         : "Outgoing Call Broadcast should not be received"
1385         );
1386     }
1387 
assertCallDetailsConstructed(Call mCall, boolean constructed)1388     void assertCallDetailsConstructed(Call mCall, boolean constructed) {
1389         waitUntilConditionIsTrueOrTimeout(
1390                 new Condition() {
1391                     @Override
1392                     public Object expected() {
1393                         return constructed;
1394                     }
1395 
1396                     @Override
1397                     public Object actual() {
1398                         return mCall != null && mCall.getDetails() != null;
1399                     }
1400                 },
1401                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1402                 constructed ? "Call Details should be constructed"
1403                         : "Call Details should not be constructed"
1404         );
1405     }
1406 
assertCallGatewayConstructed(Call mCall, boolean constructed)1407     void assertCallGatewayConstructed(Call mCall, boolean constructed) {
1408         waitUntilConditionIsTrueOrTimeout(
1409                 new Condition() {
1410                     @Override
1411                     public Object expected() {
1412                         return constructed;
1413                     }
1414 
1415                     @Override
1416                     public Object actual() {
1417                         return mCall != null && mCall.getDetails() != null
1418                                 && mCall.getDetails().getGatewayInfo() != null;
1419                     }
1420                 },
1421                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1422                 constructed ? "Call Gateway should be constructed"
1423                         : "Call Gateway should not be constructed"
1424         );
1425     }
1426 
assertCallNotNull(Call mCall, boolean notNull)1427     void assertCallNotNull(Call mCall, boolean notNull) {
1428         waitUntilConditionIsTrueOrTimeout(
1429                 new Condition() {
1430                     @Override
1431                     public Object expected() {
1432                         return notNull;
1433                     }
1434 
1435                     @Override
1436                     public Object actual() {
1437                         return mCall != null;
1438                     }
1439                 },
1440                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1441                 notNull ? "Call should not be null" : "Call should be null"
1442         );
1443     }
1444 
1445     /**
1446      * Checks all fields of two PhoneAccounts for equality, with the exception of the enabled state.
1447      * Should only be called after assertPhoneAccountRegistered when it can be guaranteed
1448      * that the PhoneAccount is registered.
1449      * @param expected The expected PhoneAccount.
1450      * @param actual The actual PhoneAccount.
1451      */
assertPhoneAccountEquals(final PhoneAccount expected, final PhoneAccount actual)1452     void assertPhoneAccountEquals(final PhoneAccount expected,
1453             final PhoneAccount actual) {
1454         assertEquals(expected.getAddress(), actual.getAddress());
1455         assertEquals(expected.getAccountHandle(), actual.getAccountHandle());
1456         assertEquals(expected.getCapabilities(), actual.getCapabilities());
1457         assertTrue(areBundlesEqual(expected.getExtras(), actual.getExtras()));
1458         assertEquals(expected.getHighlightColor(), actual.getHighlightColor());
1459         assertEquals(expected.getIcon(), actual.getIcon());
1460         assertEquals(expected.getLabel(), actual.getLabel());
1461         assertEquals(expected.getShortDescription(), actual.getShortDescription());
1462         assertEquals(expected.getSubscriptionAddress(), actual.getSubscriptionAddress());
1463         assertEquals(expected.getSupportedUriSchemes(), actual.getSupportedUriSchemes());
1464     }
1465 
assertPhoneAccountRegistered(final PhoneAccountHandle handle)1466     void assertPhoneAccountRegistered(final PhoneAccountHandle handle) {
1467         waitUntilConditionIsTrueOrTimeout(
1468                 new Condition() {
1469                     @Override
1470                     public Object expected() {
1471                         return true;
1472                     }
1473 
1474                     @Override
1475                     public Object actual() {
1476                         return mTelecomManager.getPhoneAccount(handle) != null;
1477                     }
1478                 },
1479                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1480                 "Phone account registration failed for " + handle
1481         );
1482     }
1483 
assertPhoneAccountEnabled(final PhoneAccountHandle handle)1484     void assertPhoneAccountEnabled(final PhoneAccountHandle handle) {
1485         waitUntilConditionIsTrueOrTimeout(
1486                 new Condition() {
1487                     @Override
1488                     public Object expected() {
1489                         return true;
1490                     }
1491 
1492                     @Override
1493                     public Object actual() {
1494                         PhoneAccount phoneAccount = mTelecomManager.getPhoneAccount(handle);
1495                         return (phoneAccount != null && phoneAccount.isEnabled());
1496                     }
1497                 },
1498                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1499                 "Phone account enable failed for " + handle
1500         );
1501     }
1502 
assertPhoneAccountIsDefault(final PhoneAccountHandle handle)1503     void assertPhoneAccountIsDefault(final PhoneAccountHandle handle) {
1504         waitUntilConditionIsTrueOrTimeout(
1505                 new Condition() {
1506                     @Override
1507                     public Object expected() {
1508                         return true;
1509                     }
1510 
1511                     @Override
1512                     public Object actual() {
1513                         PhoneAccountHandle phoneAccountHandle =
1514                                 mTelecomManager.getUserSelectedOutgoingPhoneAccount();
1515                         return (phoneAccountHandle != null && phoneAccountHandle.equals(handle));
1516                     }
1517                 },
1518                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1519                 "Failed to set default phone account to " + handle
1520         );
1521     }
1522 
assertCtsConnectionServiceUnbound()1523     void assertCtsConnectionServiceUnbound() {
1524         if (CtsConnectionService.isBound()) {
1525             assertTrue("CtsConnectionService not yet unbound!",
1526                     CtsConnectionService.waitForUnBinding());
1527         }
1528     }
1529 
assertMockInCallServiceUnbound()1530     void assertMockInCallServiceUnbound() {
1531         waitUntilConditionIsTrueOrTimeout(
1532                 new Condition() {
1533                     @Override
1534                     public Object expected() {
1535                         return false;
1536                     }
1537 
1538                     @Override
1539                     public Object actual() {
1540                         return MockInCallService.isServiceBound();
1541                     }
1542                 },
1543                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1544                 "MockInCallService not yet unbound!"
1545         );
1546     }
1547 
assertIsOutgoingCallPermitted(boolean isPermitted, PhoneAccountHandle handle)1548     void assertIsOutgoingCallPermitted(boolean isPermitted, PhoneAccountHandle handle) {
1549         waitUntilConditionIsTrueOrTimeout(
1550                 new Condition() {
1551                     @Override
1552                     public Object expected() {
1553                         return isPermitted;
1554                     }
1555 
1556                     @Override
1557                     public Object actual() {
1558                         return mTelecomManager.isOutgoingCallPermitted(handle);
1559                     }
1560                 },
1561                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1562                 "Expected isOutgoingCallPermitted to be " + isPermitted
1563         );
1564     }
1565 
assertIsIncomingCallPermitted(boolean isPermitted, PhoneAccountHandle handle)1566     void assertIsIncomingCallPermitted(boolean isPermitted, PhoneAccountHandle handle) {
1567         waitUntilConditionIsTrueOrTimeout(
1568                 new Condition() {
1569                     @Override
1570                     public Object expected() {
1571                         return isPermitted;
1572                     }
1573 
1574                     @Override
1575                     public Object actual() {
1576                         return mTelecomManager.isIncomingCallPermitted(handle);
1577                     }
1578                 },
1579                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1580                 "Expected isIncomingCallPermitted to be " + isPermitted
1581         );
1582     }
1583 
assertIsInCall(boolean isIncall)1584     void assertIsInCall(boolean isIncall) {
1585         waitUntilConditionIsTrueOrTimeout(
1586                 new Condition() {
1587                     @Override
1588                     public Object expected() {
1589                         return isIncall;
1590                     }
1591 
1592                     @Override
1593                     public Object actual() {
1594                         return mTelecomManager.isInCall();
1595                     }
1596                 },
1597                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1598                 "Expected isInCall to be " + isIncall
1599         );
1600     }
1601 
assertIsInManagedCall(boolean isIncall)1602     void assertIsInManagedCall(boolean isIncall) {
1603         waitUntilConditionIsTrueOrTimeout(
1604                 new Condition() {
1605                     @Override
1606                     public Object expected() {
1607                         return isIncall;
1608                     }
1609 
1610                     @Override
1611                     public Object actual() {
1612                         return mTelecomManager.isInManagedCall();
1613                     }
1614                 },
1615                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1616                 "Expected isInManagedCall to be " + isIncall
1617         );
1618     }
1619 
1620     /**
1621      * Asserts that a call's properties are as expected.
1622      *
1623      * @param call The call.
1624      * @param properties The expected properties.
1625      */
assertCallProperties(final Call call, final int properties)1626     public void assertCallProperties(final Call call, final int properties) {
1627         waitUntilConditionIsTrueOrTimeout(
1628                 new Condition() {
1629                     @Override
1630                     public Object expected() {
1631                         return true;
1632                     }
1633 
1634                     @Override
1635                     public Object actual() {
1636                         return call.getDetails().hasProperty(properties);
1637                     }
1638                 },
1639                 TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1640                 "Call should have properties " + properties
1641         );
1642     }
1643 
1644     /**
1645      * Asserts that a call does not have any of the specified call capability bits specified.
1646      *
1647      * @param call The call.
1648      * @param capabilities The capability or capabilities which are not expected.
1649      */
assertDoesNotHaveCallCapabilities(final Call call, final int capabilities)1650     public void assertDoesNotHaveCallCapabilities(final Call call, final int capabilities) {
1651         waitUntilConditionIsTrueOrTimeout(
1652                 new Condition() {
1653                     @Override
1654                     public Object expected() {
1655                         return true;
1656                     }
1657 
1658                     @Override
1659                     public Object actual() {
1660                         int callCapabilities = call.getDetails().getCallCapabilities();
1661                         return !Call.Details.hasProperty(callCapabilities, capabilities);
1662                     }
1663                 },
1664                 TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1665                 "Call should not have capabilities " + capabilities
1666         );
1667     }
1668 
1669     /**
1670      * Asserts that a call does not have any of the specified call property bits specified.
1671      *
1672      * @param call The call.
1673      * @param properties The property or properties which are not expected.
1674      */
assertDoesNotHaveCallProperties(final Call call, final int properties)1675     public void assertDoesNotHaveCallProperties(final Call call, final int properties) {
1676         waitUntilConditionIsTrueOrTimeout(
1677                 new Condition() {
1678                     @Override
1679                     public Object expected() {
1680                         return true;
1681                     }
1682 
1683                     @Override
1684                     public Object actual() {
1685                         return !call.getDetails().hasProperty(properties);
1686                     }
1687                 },
1688                 TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1689                 "Call should not have properties " + properties
1690         );
1691     }
1692 
1693     /**
1694      * Asserts that the audio manager reports the specified audio mode.
1695      *
1696      * @param audioManager The audio manager to check.
1697      * @param expectedMode The expected audio mode.
1698      */
assertAudioMode(final AudioManager audioManager, final int expectedMode)1699     public void assertAudioMode(final AudioManager audioManager, final int expectedMode) {
1700         waitUntilConditionIsTrueOrTimeout(
1701                 new Condition() {
1702                     @Override
1703                     public Object expected() {
1704                         return true;
1705                     }
1706 
1707                     @Override
1708                     public Object actual() {
1709                         return audioManager.getMode() == expectedMode;
1710                     }
1711                 },
1712                 TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1713                 "Audio mode was expected to be " + expectedMode
1714         );
1715     }
1716 
1717     /**
1718      * Asserts that a call's capabilities are as expected.
1719      *
1720      * @param call The call.
1721      * @param capabilities The expected capabiltiies.
1722      */
assertCallCapabilities(final Call call, final int capabilities)1723     public void assertCallCapabilities(final Call call, final int capabilities) {
1724         waitUntilConditionIsTrueOrTimeout(
1725                 new Condition() {
1726                     @Override
1727                     public Object expected() {
1728                         return true;
1729                     }
1730 
1731                     @Override
1732                     public Object actual() {
1733                         return (call.getDetails().getCallCapabilities() & capabilities) ==
1734                                 capabilities;
1735                     }
1736                 },
1737                 TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1738                 "Call should have properties " + capabilities
1739         );
1740     }
1741 
getInCallService()1742     MockInCallService getInCallService() {
1743         return (mInCallCallbacks == null) ? null : mInCallCallbacks.getService();
1744     }
1745 
1746     /**
1747      * Asserts that the {@link UiModeManager} mode matches the specified mode.
1748      *
1749      * @param uiMode The expected ui mode.
1750      */
assertUiMode(final int uiMode)1751     public void assertUiMode(final int uiMode) {
1752         final UiModeManager uiModeManager = mContext.getSystemService(UiModeManager.class);
1753         waitUntilConditionIsTrueOrTimeout(
1754                 new Condition() {
1755                     @Override
1756                     public Object expected() {
1757                         return uiMode;
1758                     }
1759 
1760                     @Override
1761                     public Object actual() {
1762                         return uiModeManager.getCurrentModeType();
1763                     }
1764                 },
1765                 TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
1766                 "Expected ui mode " + uiMode
1767         );
1768     }
1769 
waitUntilConditionIsTrueOrTimeout(Condition condition, long timeout, String description)1770     void waitUntilConditionIsTrueOrTimeout(Condition condition, long timeout,
1771             String description) {
1772         final long start = System.currentTimeMillis();
1773         while (!condition.expected().equals(condition.actual())
1774                 && System.currentTimeMillis() - start < timeout) {
1775             sleep(50);
1776         }
1777         assertEquals(description, condition.expected(), condition.actual());
1778     }
1779 
1780     /**
1781      * Performs some work, and waits for the condition to be met.  If the condition is not met in
1782      * each step of the loop, the work is performed again.
1783      *
1784      * @param work The work to perform.
1785      * @param condition The condition.
1786      * @param timeout The timeout.
1787      * @param description Description of the work being performed.
1788      */
doWorkAndWaitUntilConditionIsTrueOrTimeout(Work work, Condition condition, long timeout, String description)1789     void doWorkAndWaitUntilConditionIsTrueOrTimeout(Work work, Condition condition, long timeout,
1790             String description) {
1791         final long start = System.currentTimeMillis();
1792         work.doWork();
1793         while (!condition.expected().equals(condition.actual())
1794                 && System.currentTimeMillis() - start < timeout) {
1795             sleep(50);
1796             work.doWork();
1797         }
1798         assertEquals(description, condition.expected(), condition.actual());
1799     }
1800 
1801     protected interface Condition {
expected()1802         Object expected();
actual()1803         Object actual();
1804     }
1805 
1806     protected interface Work {
doWork()1807         void doWork();
1808     }
1809 
areBundlesEqual(Bundle extras, Bundle newExtras)1810     public static boolean areBundlesEqual(Bundle extras, Bundle newExtras) {
1811         if (extras == null || newExtras == null) {
1812             return extras == newExtras;
1813         }
1814 
1815         if (extras.size() != newExtras.size()) {
1816             return false;
1817         }
1818 
1819         for (String key : extras.keySet()) {
1820             if (key != null) {
1821                 final Object value = extras.get(key);
1822                 final Object newValue = newExtras.get(key);
1823                 if (!Objects.equals(value, newValue)) {
1824                     return false;
1825                 }
1826             }
1827         }
1828         return true;
1829     }
1830 }
1831