| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.telecom.cts; |
| |
| import static android.telecom.cts.TestUtils.PACKAGE; |
| import static android.telecom.cts.TestUtils.TAG; |
| import static android.telecom.cts.TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS; |
| |
| import static org.hamcrest.CoreMatchers.equalTo; |
| import static org.hamcrest.CoreMatchers.not; |
| import static org.junit.Assert.assertNotEquals; |
| import static org.junit.Assert.assertThat; |
| |
| import android.app.AppOpsManager; |
| import android.app.UiAutomation; |
| import android.app.UiModeManager; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.res.Configuration; |
| import android.content.pm.PackageManager; |
| import android.database.ContentObserver; |
| import android.database.Cursor; |
| import android.media.AudioManager; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.os.Looper; |
| import android.os.RemoteException; |
| import android.os.Process; |
| import android.os.UserHandle; |
| import android.provider.CallLog; |
| import android.telecom.Call; |
| import android.telecom.CallAudioState; |
| import android.telecom.Conference; |
| import android.telecom.Connection; |
| import android.telecom.ConnectionRequest; |
| import android.telecom.InCallService; |
| import android.telecom.PhoneAccount; |
| import android.telecom.PhoneAccountHandle; |
| import android.telecom.TelecomManager; |
| import android.telecom.VideoProfile; |
| import android.telecom.cts.MockInCallService.InCallServiceCallbacks; |
| import android.telecom.cts.carmodetestapp.ICtsCarModeInCallServiceControl; |
| import android.telephony.PhoneStateListener; |
| import android.telephony.TelephonyCallback; |
| import android.telephony.TelephonyManager; |
| import android.telephony.emergency.EmergencyNumber; |
| import android.test.InstrumentationTestCase; |
| import android.text.TextUtils; |
| import android.util.Log; |
| import android.util.Pair; |
| |
| import androidx.test.InstrumentationRegistry; |
| |
| import com.android.compatibility.common.util.ShellIdentityUtils; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Random; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.LinkedBlockingQueue; |
| import java.util.concurrent.Semaphore; |
| import java.util.concurrent.TimeUnit; |
| import java.util.stream.Collectors; |
| |
| /** |
| * Base class for Telecom CTS tests that require a {@link CtsConnectionService} and |
| * {@link MockInCallService} to verify Telecom functionality. |
| */ |
| public class BaseTelecomTestWithMockServices extends InstrumentationTestCase { |
| |
| public static final int FLAG_REGISTER = 0x1; |
| public static final int FLAG_ENABLE = 0x2; |
| public static final int FLAG_SET_DEFAULT = 0x4; |
| |
| // Don't accidently use emergency number. |
| private static int sCounter = 5553638; |
| |
| public static final String TEST_EMERGENCY_NUMBER = "5553637"; |
| public static final Uri TEST_EMERGENCY_URI = Uri.fromParts("tel", TEST_EMERGENCY_NUMBER, null); |
| public static final String PKG_NAME = "android.telecom.cts"; |
| public static final String PERMISSION_PROCESS_OUTGOING_CALLS = |
| "android.permission.PROCESS_OUTGOING_CALLS"; |
| |
| Context mContext; |
| TelecomManager mTelecomManager; |
| TelephonyManager mTelephonyManager; |
| UiModeManager mUiModeManager; |
| |
| TestUtils.InvokeCounter mOnBringToForegroundCounter; |
| TestUtils.InvokeCounter mOnCallAudioStateChangedCounter; |
| TestUtils.InvokeCounter mOnPostDialWaitCounter; |
| TestUtils.InvokeCounter mOnCannedTextResponsesLoadedCounter; |
| TestUtils.InvokeCounter mOnSilenceRingerCounter; |
| TestUtils.InvokeCounter mOnConnectionEventCounter; |
| TestUtils.InvokeCounter mOnExtrasChangedCounter; |
| TestUtils.InvokeCounter mOnPropertiesChangedCounter; |
| TestUtils.InvokeCounter mOnRttModeChangedCounter; |
| TestUtils.InvokeCounter mOnRttStatusChangedCounter; |
| TestUtils.InvokeCounter mOnRttInitiationFailedCounter; |
| TestUtils.InvokeCounter mOnRttRequestCounter; |
| TestUtils.InvokeCounter mOnHandoverCompleteCounter; |
| TestUtils.InvokeCounter mOnHandoverFailedCounter; |
| TestUtils.InvokeCounter mOnPhoneAccountChangedCounter; |
| Bundle mPreviousExtras; |
| int mPreviousProperties = -1; |
| PhoneAccountHandle mPreviousPhoneAccountHandle = null; |
| |
| InCallServiceCallbacks mInCallCallbacks; |
| String mPreviousDefaultDialer = null; |
| PhoneAccountHandle mPreviousDefaultOutgoingAccount = null; |
| boolean mShouldRestoreDefaultOutgoingAccount = false; |
| MockConnectionService connectionService = null; |
| boolean mIsEmergencyCallingSetup = false; |
| |
| HandlerThread mTelephonyCallbackThread; |
| Handler mTelephonyCallbackHandler; |
| TestTelephonyCallback mTelephonyCallback; |
| TestCallStateListener mTestCallStateListener; |
| Handler mHandler; |
| |
| /** |
| * Uses the control interface to disable car mode. |
| * @param expectedUiMode |
| */ |
| protected void disableAndVerifyCarMode(ICtsCarModeInCallServiceControl control, |
| int expectedUiMode) { |
| if (control == null) { |
| return; |
| } |
| try { |
| control.disableCarMode(); |
| } catch (RemoteException re) { |
| fail("Bee-boop; can't control the incall service"); |
| } |
| assertUiMode(expectedUiMode); |
| } |
| |
| protected void disconnectAllCallsAndVerify(ICtsCarModeInCallServiceControl controlBinder) { |
| if (controlBinder == null) { |
| return; |
| } |
| try { |
| controlBinder.disconnectCalls(); |
| } catch (RemoteException re) { |
| fail("Bee-boop; can't control the incall service"); |
| } |
| assertCarModeCallCount(controlBinder, 0); |
| } |
| |
| /** |
| * Verify the car mode ICS has an expected call count. |
| * @param expected |
| */ |
| protected void assertCarModeCallCount(ICtsCarModeInCallServiceControl control, int expected) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return expected; |
| } |
| |
| @Override |
| public Object actual() { |
| int callCount = 0; |
| try { |
| callCount = control.getCallCount(); |
| } catch (RemoteException re) { |
| fail("Bee-boop; can't control the incall service"); |
| } |
| return callCount; |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Expected " + expected + " calls." |
| ); |
| } |
| |
| static class TestCallStateListener extends TelephonyCallback |
| implements TelephonyCallback.CallStateListener { |
| |
| private CountDownLatch mCountDownLatch = new CountDownLatch(1); |
| private int mLastState = -1; |
| |
| @Override |
| public void onCallStateChanged(int state) { |
| Log.i(TAG, "onCallStateChanged: state=" + state); |
| mLastState = state; |
| mCountDownLatch.countDown(); |
| mCountDownLatch = new CountDownLatch(1); |
| } |
| |
| public CountDownLatch getCountDownLatch() { |
| return mCountDownLatch; |
| } |
| |
| public int getLastState() { |
| return mLastState; |
| } |
| } |
| |
| static class TestTelephonyCallback extends TelephonyCallback implements |
| TelephonyCallback.CallStateListener, |
| TelephonyCallback.OutgoingEmergencyCallListener, |
| TelephonyCallback.EmergencyNumberListListener { |
| /** Semaphore released for every callback invocation. */ |
| public Semaphore mCallbackSemaphore = new Semaphore(0); |
| |
| List<Integer> mCallStates = new ArrayList<>(); |
| EmergencyNumber mLastOutgoingEmergencyNumber; |
| |
| LinkedBlockingQueue<Map<Integer, List<EmergencyNumber>>> mEmergencyNumberListQueue = |
| new LinkedBlockingQueue<>(2); |
| |
| @Override |
| public void onCallStateChanged(int state) { |
| Log.i(TAG, "onCallStateChanged: state=" + state); |
| mCallStates.add(state); |
| mCallbackSemaphore.release(); |
| } |
| |
| @Override |
| public void onOutgoingEmergencyCall(EmergencyNumber emergencyNumber, int subscriptionId) { |
| Log.i(TAG, "onOutgoingEmergencyCall: emergencyNumber=" + emergencyNumber); |
| mLastOutgoingEmergencyNumber = emergencyNumber; |
| mCallbackSemaphore.release(); |
| } |
| |
| @Override |
| public void onEmergencyNumberListChanged( |
| Map<Integer, List<EmergencyNumber>> emergencyNumberList) { |
| Log.i(TAG, "onEmergencyNumberChanged, total size=" + emergencyNumberList.values() |
| .stream().mapToInt(List::size).sum()); |
| mEmergencyNumberListQueue.offer(emergencyNumberList); |
| } |
| |
| public Map<Integer, List<EmergencyNumber>> waitForEmergencyNumberListUpdate( |
| long timeoutMillis) throws Throwable { |
| return mEmergencyNumberListQueue.poll(timeoutMillis, TimeUnit.MILLISECONDS); |
| } |
| } |
| |
| boolean mShouldTestTelecom = true; |
| |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| mContext = getInstrumentation().getContext(); |
| mHandler = new Handler(Looper.getMainLooper()); |
| mShouldTestTelecom = TestUtils.shouldTestTelecom(mContext); |
| if (!mShouldTestTelecom) { |
| return; |
| } |
| |
| // Assume we start in normal mode at the start of all Telecom tests; a failure to leave car |
| // mode in any of the tests would cause subsequent test failures. |
| // For Watch, UI_MODE shouldn't be normal mode. |
| mUiModeManager = mContext.getSystemService(UiModeManager.class); |
| TestUtils.executeShellCommand(getInstrumentation(), "telecom reset-car-mode"); |
| |
| if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) { |
| assertUiMode(Configuration.UI_MODE_TYPE_WATCH); |
| } else { |
| assertUiMode(Configuration.UI_MODE_TYPE_NORMAL); |
| } |
| |
| AppOpsManager aom = mContext.getSystemService(AppOpsManager.class); |
| ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(aom, |
| (appOpsMan) -> appOpsMan.setUidMode(AppOpsManager.OPSTR_PROCESS_OUTGOING_CALLS, |
| Process.myUid(), AppOpsManager.MODE_ALLOWED)); |
| |
| mTelecomManager = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE); |
| mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); |
| |
| mPreviousDefaultDialer = TestUtils.getDefaultDialer(getInstrumentation()); |
| TestUtils.setDefaultDialer(getInstrumentation(), PACKAGE); |
| setupCallbacks(); |
| |
| // Register a call state listener. |
| mTestCallStateListener = new TestCallStateListener(); |
| CountDownLatch latch = mTestCallStateListener.getCountDownLatch(); |
| mTelephonyManager.registerTelephonyCallback(r -> r.run(), mTestCallStateListener); |
| latch.await( |
| TestUtils.WAIT_FOR_PHONE_STATE_LISTENER_REGISTERED_TIMEOUT_S, TimeUnit.SECONDS); |
| // Create a new thread for the telephony callback. |
| mTelephonyCallbackThread = new HandlerThread("PhoneStateListenerThread"); |
| mTelephonyCallbackThread.start(); |
| mTelephonyCallbackHandler = new Handler(mTelephonyCallbackThread.getLooper()); |
| |
| mTelephonyCallback = new TestTelephonyCallback(); |
| ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager, |
| (tm) -> tm.registerTelephonyCallback( |
| mTelephonyCallbackHandler::post, |
| mTelephonyCallback)); |
| UiAutomation uiAutomation = |
| InstrumentationRegistry.getInstrumentation().getUiAutomation(); |
| uiAutomation.grantRuntimePermissionAsUser(PKG_NAME, PERMISSION_PROCESS_OUTGOING_CALLS, |
| UserHandle.CURRENT); |
| } |
| |
| @Override |
| protected void tearDown() throws Exception { |
| super.tearDown(); |
| if (!mShouldTestTelecom) { |
| return; |
| } |
| |
| mTelephonyManager.unregisterTelephonyCallback(mTestCallStateListener); |
| |
| mTelephonyManager.unregisterTelephonyCallback(mTelephonyCallback); |
| mTelephonyCallbackThread.quit(); |
| |
| cleanupCalls(); |
| if (!TextUtils.isEmpty(mPreviousDefaultDialer)) { |
| TestUtils.setDefaultDialer(getInstrumentation(), mPreviousDefaultDialer); |
| } |
| tearDownConnectionService(TestUtils.TEST_PHONE_ACCOUNT_HANDLE); |
| tearDownEmergencyCalling(); |
| try { |
| assertMockInCallServiceUnbound(); |
| } catch (Throwable t) { |
| // If we haven't unbound, that means there's some dirty state in Telecom that needs |
| // cleaning up. Forcibly unbind and clean up Telecom state so that we don't have a |
| // cascading failure of tests. |
| TestUtils.executeShellCommand(getInstrumentation(), "telecom cleanup-stuck-calls"); |
| throw t; |
| } |
| UiAutomation uiAutomation = |
| InstrumentationRegistry.getInstrumentation().getUiAutomation(); |
| uiAutomation.revokeRuntimePermissionAsUser(PKG_NAME, PERMISSION_PROCESS_OUTGOING_CALLS, |
| UserHandle.CURRENT); |
| } |
| |
| protected PhoneAccount setupConnectionService(MockConnectionService connectionService, |
| int flags) throws Exception { |
| Log.i(TAG, "Setting up mock connection service"); |
| if (connectionService != null) { |
| this.connectionService = connectionService; |
| } else { |
| // Generate a vanilla mock connection service, if not provided. |
| this.connectionService = new MockConnectionService(); |
| } |
| CtsConnectionService.setUp(this.connectionService); |
| |
| if ((flags & FLAG_REGISTER) != 0) { |
| mTelecomManager.registerPhoneAccount(TestUtils.TEST_PHONE_ACCOUNT); |
| } |
| if ((flags & FLAG_ENABLE) != 0) { |
| TestUtils.enablePhoneAccount(getInstrumentation(), TestUtils.TEST_PHONE_ACCOUNT_HANDLE); |
| // Wait till the adb commands have executed and account is enabled in Telecom database. |
| assertPhoneAccountEnabled(TestUtils.TEST_PHONE_ACCOUNT_HANDLE); |
| } |
| |
| if ((flags & FLAG_SET_DEFAULT) != 0) { |
| mPreviousDefaultOutgoingAccount = mTelecomManager.getUserSelectedOutgoingPhoneAccount(); |
| mShouldRestoreDefaultOutgoingAccount = true; |
| TestUtils.setDefaultOutgoingPhoneAccount(getInstrumentation(), |
| TestUtils.TEST_PHONE_ACCOUNT_HANDLE); |
| // Wait till the adb commands have executed and the default has changed. |
| assertPhoneAccountIsDefault(TestUtils.TEST_PHONE_ACCOUNT_HANDLE); |
| } |
| |
| return TestUtils.TEST_PHONE_ACCOUNT; |
| } |
| |
| protected void tearDownConnectionService(PhoneAccountHandle accountHandle) throws Exception { |
| Log.i(TAG, "Tearing down mock connection service"); |
| if (this.connectionService != null) { |
| assertNumConnections(this.connectionService, 0); |
| } |
| mTelecomManager.unregisterPhoneAccount(accountHandle); |
| CtsConnectionService.tearDown(); |
| assertCtsConnectionServiceUnbound(); |
| if (mShouldRestoreDefaultOutgoingAccount) { |
| TestUtils.setDefaultOutgoingPhoneAccount(getInstrumentation(), |
| mPreviousDefaultOutgoingAccount); |
| } |
| this.connectionService = null; |
| mPreviousDefaultOutgoingAccount = null; |
| mShouldRestoreDefaultOutgoingAccount = false; |
| } |
| |
| protected void setupForEmergencyCalling(String testNumber) throws Exception { |
| TestUtils.setSystemDialerOverride(getInstrumentation()); |
| TestUtils.addTestEmergencyNumber(getInstrumentation(), testNumber); |
| TestUtils.setTestEmergencyPhoneAccountPackageFilter(getInstrumentation(), mContext); |
| // Emergency calls require special capabilities. |
| TestUtils.registerEmergencyPhoneAccount(getInstrumentation(), |
| TestUtils.TEST_EMERGENCY_PHONE_ACCOUNT_HANDLE, |
| TestUtils.ACCOUNT_LABEL + "E", "tel:555-EMER"); |
| mIsEmergencyCallingSetup = true; |
| } |
| |
| protected void tearDownEmergencyCalling() throws Exception { |
| if (!mIsEmergencyCallingSetup) return; |
| |
| TestUtils.clearSystemDialerOverride(getInstrumentation()); |
| TestUtils.clearTestEmergencyNumbers(getInstrumentation()); |
| TestUtils.clearTestEmergencyPhoneAccountPackageFilter(getInstrumentation()); |
| mTelecomManager.unregisterPhoneAccount(TestUtils.TEST_EMERGENCY_PHONE_ACCOUNT_HANDLE); |
| } |
| |
| protected void startCallTo(Uri address, PhoneAccountHandle accountHandle) { |
| final Intent intent = new Intent(Intent.ACTION_CALL, address); |
| if (accountHandle != null) { |
| intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, accountHandle); |
| } |
| intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| mContext.startActivity(intent); |
| } |
| |
| void sleep(long ms) { |
| try { |
| Thread.sleep(ms); |
| } catch (InterruptedException e) { |
| } |
| } |
| |
| private void setupCallbacks() { |
| mInCallCallbacks = new InCallServiceCallbacks() { |
| @Override |
| public void onCallAdded(Call call, int numCalls) { |
| Log.i(TAG, "onCallAdded, Call: " + call + ", Num Calls: " + numCalls); |
| this.lock.release(); |
| mPreviousPhoneAccountHandle = call.getDetails().getAccountHandle(); |
| } |
| @Override |
| public void onCallRemoved(Call call, int numCalls) { |
| Log.i(TAG, "onCallRemoved, Call: " + call + ", Num Calls: " + numCalls); |
| } |
| @Override |
| public void onParentChanged(Call call, Call parent) { |
| Log.i(TAG, "onParentChanged, Call: " + call + ", Parent: " + parent); |
| this.lock.release(); |
| } |
| @Override |
| public void onChildrenChanged(Call call, List<Call> children) { |
| Log.i(TAG, "onChildrenChanged, Call: " + call + "Children: " + children); |
| this.lock.release(); |
| } |
| @Override |
| public void onConferenceableCallsChanged(Call call, List<Call> conferenceableCalls) { |
| Log.i(TAG, "onConferenceableCallsChanged, Call: " + call + ", Conferenceables: " + |
| conferenceableCalls); |
| } |
| @Override |
| public void onDetailsChanged(Call call, Call.Details details) { |
| Log.i(TAG, "onDetailsChanged, Call: " + call + ", Details: " + details); |
| if (!areBundlesEqual(mPreviousExtras, details.getExtras())) { |
| mOnExtrasChangedCounter.invoke(call, details); |
| } |
| mPreviousExtras = details.getExtras(); |
| |
| if (mPreviousProperties != details.getCallProperties()) { |
| mOnPropertiesChangedCounter.invoke(call, details); |
| Log.i(TAG, "onDetailsChanged; properties changed from " + Call.Details.propertiesToString(mPreviousProperties) + |
| " to " + Call.Details.propertiesToString(details.getCallProperties())); |
| } |
| mPreviousProperties = details.getCallProperties(); |
| |
| if (details.getAccountHandle() != null && |
| !details.getAccountHandle().equals(mPreviousPhoneAccountHandle)) { |
| mOnPhoneAccountChangedCounter.invoke(call, details.getAccountHandle()); |
| } |
| mPreviousPhoneAccountHandle = details.getAccountHandle(); |
| } |
| @Override |
| public void onCallDestroyed(Call call) { |
| Log.i(TAG, "onCallDestroyed, Call: " + call); |
| } |
| @Override |
| public void onCallStateChanged(Call call, int newState) { |
| Log.i(TAG, "onCallStateChanged, Call: " + call + ", New State: " + newState); |
| } |
| @Override |
| public void onBringToForeground(boolean showDialpad) { |
| mOnBringToForegroundCounter.invoke(showDialpad); |
| } |
| @Override |
| public void onCallAudioStateChanged(CallAudioState audioState) { |
| Log.i(TAG, "onCallAudioStateChanged, audioState: " + audioState); |
| mOnCallAudioStateChangedCounter.invoke(audioState); |
| } |
| @Override |
| public void onPostDialWait(Call call, String remainingPostDialSequence) { |
| mOnPostDialWaitCounter.invoke(call, remainingPostDialSequence); |
| } |
| @Override |
| public void onCannedTextResponsesLoaded(Call call, List<String> cannedTextResponses) { |
| mOnCannedTextResponsesLoadedCounter.invoke(call, cannedTextResponses); |
| } |
| @Override |
| public void onConnectionEvent(Call call, String event, Bundle extras) { |
| mOnConnectionEventCounter.invoke(call, event, extras); |
| } |
| |
| @Override |
| public void onSilenceRinger() { |
| Log.i(TAG, "onSilenceRinger"); |
| mOnSilenceRingerCounter.invoke(); |
| } |
| |
| @Override |
| public void onRttModeChanged(Call call, int mode) { |
| mOnRttModeChangedCounter.invoke(call, mode); |
| } |
| |
| @Override |
| public void onRttStatusChanged(Call call, boolean enabled, Call.RttCall rttCall) { |
| mOnRttStatusChangedCounter.invoke(call, enabled, rttCall); |
| } |
| |
| @Override |
| public void onRttRequest(Call call, int id) { |
| mOnRttRequestCounter.invoke(call, id); |
| } |
| |
| @Override |
| public void onRttInitiationFailure(Call call, int reason) { |
| mOnRttInitiationFailedCounter.invoke(call, reason); |
| } |
| |
| @Override |
| public void onHandoverComplete(Call call) { |
| mOnHandoverCompleteCounter.invoke(call); |
| } |
| |
| @Override |
| public void onHandoverFailed(Call call, int reason) { |
| mOnHandoverFailedCounter.invoke(call, reason); |
| } |
| }; |
| |
| MockInCallService.setCallbacks(mInCallCallbacks); |
| |
| // TODO: If more InvokeCounters are added in the future, consider consolidating them into a |
| // single Collection. |
| mOnBringToForegroundCounter = new TestUtils.InvokeCounter("OnBringToForeground"); |
| mOnCallAudioStateChangedCounter = new TestUtils.InvokeCounter("OnCallAudioStateChanged"); |
| mOnPostDialWaitCounter = new TestUtils.InvokeCounter("OnPostDialWait"); |
| mOnCannedTextResponsesLoadedCounter = new TestUtils.InvokeCounter("OnCannedTextResponsesLoaded"); |
| mOnSilenceRingerCounter = new TestUtils.InvokeCounter("OnSilenceRinger"); |
| mOnConnectionEventCounter = new TestUtils.InvokeCounter("OnConnectionEvent"); |
| mOnExtrasChangedCounter = new TestUtils.InvokeCounter("OnDetailsChangedCounter"); |
| mOnPropertiesChangedCounter = new TestUtils.InvokeCounter("OnPropertiesChangedCounter"); |
| mOnRttModeChangedCounter = new TestUtils.InvokeCounter("mOnRttModeChangedCounter"); |
| mOnRttStatusChangedCounter = new TestUtils.InvokeCounter("mOnRttStatusChangedCounter"); |
| mOnRttInitiationFailedCounter = |
| new TestUtils.InvokeCounter("mOnRttInitiationFailedCounter"); |
| mOnRttRequestCounter = new TestUtils.InvokeCounter("mOnRttRequestCounter"); |
| mOnHandoverCompleteCounter = new TestUtils.InvokeCounter("mOnHandoverCompleteCounter"); |
| mOnHandoverFailedCounter = new TestUtils.InvokeCounter("mOnHandoverFailedCounter"); |
| mOnPhoneAccountChangedCounter = new TestUtils.InvokeCounter( |
| "mOnPhoneAccountChangedCounter"); |
| } |
| |
| void addAndVerifyNewFailedIncomingCall(Uri incomingHandle, Bundle extras) { |
| assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits()); |
| int currentCallCount = 0; |
| if (mInCallCallbacks.getService() != null) { |
| currentCallCount = mInCallCallbacks.getService().getCallCount(); |
| } |
| |
| if (extras == null) { |
| extras = new Bundle(); |
| } |
| extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, incomingHandle); |
| mTelecomManager.addNewIncomingCall(TestUtils.TEST_PHONE_ACCOUNT_HANDLE, extras); |
| |
| if (!connectionService.waitForEvent( |
| MockConnectionService.EVENT_CONNECTION_SERVICE_CREATE_CONNECTION_FAILED)) { |
| fail("Incoming Connection failure indication did not get called."); |
| } |
| |
| assertEquals("ConnectionService did not receive failed connection", |
| 1, connectionService.failedConnections.size()); |
| |
| assertEquals("Address is not correct for failed connection", |
| connectionService.failedConnections.get(0).getAddress(), incomingHandle); |
| |
| assertEquals("InCallService should contain the same number of calls.", |
| currentCallCount, |
| mInCallCallbacks.getService().getCallCount()); |
| } |
| |
| /** |
| * Puts Telecom in a state where there is an incoming call provided by the |
| * {@link CtsConnectionService} which can be tested. |
| */ |
| void addAndVerifyNewIncomingCall(Uri incomingHandle, Bundle extras) { |
| int currentCallCount = addNewIncomingCall(incomingHandle, extras); |
| verifyNewIncomingCall(currentCallCount); |
| } |
| |
| int addNewIncomingCall(Uri incomingHandle, Bundle extras) { |
| assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits()); |
| int currentCallCount = 0; |
| if (mInCallCallbacks.getService() != null) { |
| currentCallCount = mInCallCallbacks.getService().getCallCount(); |
| } |
| |
| if (extras == null) { |
| extras = new Bundle(); |
| } |
| extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, incomingHandle); |
| mTelecomManager.addNewIncomingCall(TestUtils.TEST_PHONE_ACCOUNT_HANDLE, extras); |
| |
| return currentCallCount; |
| } |
| |
| void verifyNewIncomingCall(int currentCallCount) { |
| try { |
| if (!mInCallCallbacks.lock.tryAcquire(TestUtils.WAIT_FOR_CALL_ADDED_TIMEOUT_S, |
| TimeUnit.SECONDS)) { |
| fail("No call added to InCallService."); |
| } |
| } catch (InterruptedException e) { |
| Log.i(TAG, "Test interrupted!"); |
| } |
| |
| assertEquals("InCallService should contain 1 more call after adding a call.", |
| currentCallCount + 1, |
| mInCallCallbacks.getService().getCallCount()); |
| } |
| |
| /** |
| * Puts Telecom in a state where there is an active call provided by the |
| * {@link CtsConnectionService} which can be tested. |
| */ |
| void placeAndVerifyCall() { |
| placeAndVerifyCall(null); |
| } |
| |
| void placeAndVerifyCallByRedirection(boolean wasCancelled) { |
| placeAndVerifyCallByRedirection(null, wasCancelled); |
| } |
| |
| /** |
| * Puts Telecom in a state where there is an active call provided by the |
| * {@link CtsConnectionService} which can be tested. |
| */ |
| void placeAndVerifyCallByRedirection(Bundle extras, boolean wasCancelled) { |
| int currentCallCount = (getInCallService() == null) ? 0 : getInCallService().getCallCount(); |
| int currentConnections = getNumberOfConnections(); |
| // We expect a new connection if it wasn't cancelled. |
| if (!wasCancelled) { |
| currentConnections++; |
| currentCallCount++; |
| } |
| placeAndVerifyCall(extras, VideoProfile.STATE_AUDIO_ONLY, currentCallCount); |
| // The connectionService.lock is released in |
| // MockConnectionService#onCreateOutgoingConnection, however the connection will not |
| // actually be added to the list of connections in the ConnectionService until shortly |
| // afterwards. So there is still a potential for the lock to be released before it would |
| // be seen by calls to ConnectionService#getAllConnections(). |
| // We will wait here until the list of connections includes one more connection to ensure |
| // that placing the call has fully completed. |
| assertCSConnections(currentConnections); |
| |
| // Ensure the new outgoing call broadcast fired for the outgoing call. |
| assertOutgoingCallBroadcastReceived(true); |
| |
| // CTS test does not have read call log permission so should not get the phone number. |
| assertNull(NewOutgoingCallBroadcastReceiver.getReceivedNumber()); |
| } |
| |
| /** |
| * Puts Telecom in a state where there is an active call provided by the |
| * {@link CtsConnectionService} which can be tested. |
| * |
| * @param videoState the video state of the call. |
| */ |
| void placeAndVerifyCall(int videoState) { |
| placeAndVerifyCall(null, videoState); |
| } |
| |
| /** |
| * Puts Telecom in a state where there is an active call provided by the |
| * {@link CtsConnectionService} which can be tested. |
| */ |
| void placeAndVerifyCall(Bundle extras) { |
| placeAndVerifyCall(extras, VideoProfile.STATE_AUDIO_ONLY); |
| } |
| |
| /** |
| * Puts Telecom in a state where there is an active call provided by the |
| * {@link CtsConnectionService} which can be tested. |
| */ |
| void placeAndVerifyCall(Bundle extras, int videoState) { |
| int currentCallCount = (getInCallService() == null) ? 0 : getInCallService().getCallCount(); |
| // We expect placing the call adds a new call/connection. |
| int expectedConnections = getNumberOfConnections() + 1; |
| placeAndVerifyCall(extras, videoState, currentCallCount + 1); |
| // The connectionService.lock is released in |
| // MockConnectionService#onCreateOutgoingConnection, however the connection will not |
| // actually be added to the list of connections in the ConnectionService until shortly |
| // afterwards. So there is still a potential for the lock to be released before it would |
| // be seen by calls to ConnectionService#getAllConnections(). |
| // We will wait here until the list of connections includes one more connection to ensure |
| // that placing the call has fully completed. |
| assertCSConnections(expectedConnections); |
| assertOutgoingCallBroadcastReceived(true); |
| |
| // CTS test does not have read call log permission so should not get the phone number. |
| assertNull(NewOutgoingCallBroadcastReceiver.getReceivedNumber()); |
| } |
| |
| /** |
| * Puts Telecom in a state where there is an active call provided by the |
| * {@link CtsConnectionService} which can be tested. |
| */ |
| void placeAndVerifyCall(Bundle extras, int videoState, int expectedCallCount) { |
| assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits()); |
| placeNewCallWithPhoneAccount(extras, videoState); |
| |
| try { |
| if (!mInCallCallbacks.lock.tryAcquire(TestUtils.WAIT_FOR_CALL_ADDED_TIMEOUT_S, |
| TimeUnit.SECONDS)) { |
| fail("No call added to InCallService."); |
| } |
| } catch (InterruptedException e) { |
| Log.i(TAG, "Test interrupted!"); |
| } |
| |
| // Make sure any procedures to disconnect existing calls (makeRoomForOutgoingCall) |
| // complete successfully |
| TestUtils.waitOnLocalMainLooper(WAIT_FOR_STATE_CHANGE_TIMEOUT_MS); |
| TestUtils.waitOnAllHandlers(getInstrumentation()); |
| |
| assertEquals("InCallService should match the expected count.", expectedCallCount, |
| mInCallCallbacks.getService().getCallCount()); |
| } |
| |
| /** |
| * Place an emergency call and verify that it has been setup properly. |
| * |
| * @param supportsHold If telecom supports holding emergency calls, this will expect two |
| * calls. If telecom does not support holding emergency calls, this will expect only the |
| * emergency call to be active. |
| * @return The emergency connection |
| */ |
| public Connection placeAndVerifyEmergencyCall(boolean supportsHold) { |
| Bundle extras = new Bundle(); |
| extras.putParcelable(TestUtils.EXTRA_PHONE_NUMBER, TEST_EMERGENCY_URI); |
| // We want to request the active connections vs number of connections because in some cases, |
| // we wait to destroy the underlying connection to prevent race conditions. This will result |
| // in Connections in the DISCONNECTED state. |
| int currentConnectionCount = supportsHold ? |
| getNumberOfActiveConnections() + 1 : getNumberOfActiveConnections(); |
| int currentCallCount = (getInCallService() == null) ? 0 : getInCallService().getCallCount(); |
| currentCallCount = supportsHold ? currentCallCount + 1 : currentCallCount; |
| // The device only supports a max of two calls active at any one time |
| currentCallCount = Math.min(currentCallCount, 2); |
| |
| placeAndVerifyCall(extras, VideoProfile.STATE_AUDIO_ONLY, currentCallCount); |
| // The connectionService.lock is released in |
| // MockConnectionService#onCreateOutgoingConnection, however the connection will not |
| // actually be added to the list of connections in the ConnectionService until shortly |
| // afterwards. So there is still a potential for the lock to be released before it would |
| // be seen by calls to ConnectionService#getAllConnections(). |
| // We will wait here until the list of connections includes one more connection to ensure |
| // that placing the call has fully completed. |
| assertActiveCSConnections(currentConnectionCount); |
| |
| assertOutgoingCallBroadcastReceived(true); |
| Connection connection = verifyConnectionForOutgoingCall(TEST_EMERGENCY_URI); |
| TestUtils.waitOnAllHandlers(getInstrumentation()); |
| return connection; |
| } |
| |
| int getNumberOfConnections() { |
| return CtsConnectionService.getAllConnectionsFromTelecom().size(); |
| } |
| |
| int getNumberOfActiveConnections() { |
| return CtsConnectionService.getAllConnectionsFromTelecom().stream() |
| .filter(c -> c.getState() != Connection.STATE_DISCONNECTED).collect( |
| Collectors.toSet()).size(); |
| } |
| |
| Connection getConnection(Uri address) { |
| return CtsConnectionService.getAllConnectionsFromTelecom().stream() |
| .filter(c -> c.getAddress().equals(address)).findFirst().orElse(null); |
| } |
| |
| MockConnection verifyConnectionForOutgoingCall() { |
| // Assuming only 1 connection present |
| return verifyConnectionForOutgoingCall(0); |
| } |
| |
| MockConnection verifyConnectionForOutgoingCall(int connectionIndex) { |
| try { |
| if (!connectionService.lock.tryAcquire(TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| TimeUnit.MILLISECONDS)) { |
| fail("No outgoing call connection requested by Telecom"); |
| } |
| } catch (InterruptedException e) { |
| Log.i(TAG, "Test interrupted!"); |
| } |
| |
| assertThat("Telecom should create outgoing connection for outgoing call", |
| connectionService.outgoingConnections.size(), not(equalTo(0))); |
| MockConnection connection = connectionService.outgoingConnections.get(connectionIndex); |
| return connection; |
| } |
| |
| MockConnection verifyConnectionForOutgoingCall(Uri address) { |
| if (!connectionService.waitForEvent( |
| MockConnectionService.EVENT_CONNECTION_SERVICE_CREATE_CONNECTION)) { |
| fail("No outgoing call connection requested by Telecom"); |
| } |
| assertThat("Telecom should create outgoing connection for outgoing call", |
| connectionService.outgoingConnections.size(), not(equalTo(0))); |
| |
| // There is a subtle race condition in ConnectionService. When onCreateIncomingConnection |
| // or onCreateOutgoingConnection completes, ConnectionService then adds the connection to |
| // the list of tracked connections. It's very possible for the lock to be released and |
| // the connection to have not yet been added to the connection list yet. |
| waitUntilConditionIsTrueOrTimeout(new Condition() { |
| @Override |
| public Object expected() { |
| return true; |
| } |
| |
| @Override |
| public Object actual() { |
| return getConnection(address) != null; |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Expected call from number " + address); |
| Connection connection = getConnection(address); |
| |
| if (connection instanceof MockConnection) { |
| if (connectionService.outgoingConnections.contains(connection)) { |
| return (MockConnection) connection; |
| } |
| } |
| return null; |
| } |
| |
| MockConnection verifyConnectionForIncomingCall() { |
| // Assuming only 1 connection present |
| return verifyConnectionForIncomingCall(0); |
| } |
| |
| MockConnection verifyConnectionForIncomingCall(int connectionIndex) { |
| try { |
| if (!connectionService.lock.tryAcquire(TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| TimeUnit.MILLISECONDS)) { |
| fail("No outgoing call connection requested by Telecom"); |
| } |
| } catch (InterruptedException e) { |
| Log.i(TAG, "Test interrupted!"); |
| } |
| |
| assertThat("Telecom should create incoming connections for incoming calls", |
| connectionService.incomingConnections.size(), not(equalTo(0))); |
| MockConnection connection = connectionService.incomingConnections.get(connectionIndex); |
| setAndVerifyConnectionForIncomingCall(connection); |
| return connection; |
| } |
| |
| MockConference verifyConference(int permit) { |
| try { |
| if (!connectionService.lock.tryAcquire(permit, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| TimeUnit.MILLISECONDS)) { |
| fail("No conference requested by Telecom"); |
| } |
| } catch (InterruptedException e) { |
| Log.i(TAG, "Test interrupted!"); |
| } |
| return connectionService.conferences.get(0); |
| } |
| |
| void setAndVerifyConnectionForIncomingCall(MockConnection connection) { |
| if (connection.getState() == Connection.STATE_ACTIVE) { |
| // If the connection is already active (like if it got picked up immediately), don't |
| // bother with setting it back to ringing. |
| return; |
| } |
| connection.setRinging(); |
| assertConnectionState(connection, Connection.STATE_RINGING); |
| } |
| |
| void setAndVerifyConferenceablesForOutgoingConnection(int connectionIndex) { |
| assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits()); |
| // Make all other outgoing connections as conferenceable with this connection. |
| MockConnection connection = connectionService.outgoingConnections.get(connectionIndex); |
| List<Connection> confConnections = |
| new ArrayList<>(connectionService.outgoingConnections.size()); |
| for (Connection c : connectionService.outgoingConnections) { |
| if (c != connection) { |
| confConnections.add(c); |
| } |
| } |
| connection.setConferenceableConnections(confConnections); |
| assertEquals(connection.getConferenceables(), confConnections); |
| } |
| |
| void addConferenceCall(Call call1, Call call2) { |
| assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits()); |
| int currentConfCallCount = 0; |
| if (mInCallCallbacks.getService() != null) { |
| currentConfCallCount = mInCallCallbacks.getService().getConferenceCallCount(); |
| } |
| // Verify that the calls have each other on their conferenceable list before proceeding |
| List<Call> callConfList = new ArrayList<>(); |
| callConfList.add(call2); |
| assertCallConferenceableList(call1, callConfList); |
| |
| callConfList.clear(); |
| callConfList.add(call1); |
| assertCallConferenceableList(call2, callConfList); |
| |
| call1.conference(call2); |
| |
| /** |
| * We should have 1 onCallAdded, 2 onChildrenChanged and 2 onParentChanged invoked, so |
| * we should have 5 available permits on the incallService lock. |
| */ |
| try { |
| if (!mInCallCallbacks.lock.tryAcquire(5, 3, TimeUnit.SECONDS)) { |
| fail("Conference addition failed."); |
| } |
| } catch (InterruptedException e) { |
| Log.i(TAG, "Test interrupted!"); |
| } |
| |
| assertEquals("InCallService should contain 1 more call after adding a conf call.", |
| currentConfCallCount + 1, |
| mInCallCallbacks.getService().getConferenceCallCount()); |
| } |
| |
| void splitFromConferenceCall(Call call1) { |
| assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits()); |
| |
| call1.splitFromConference(); |
| /** |
| * We should have 1 onChildrenChanged and 1 onParentChanged invoked, so |
| * we should have 2 available permits on the incallService lock. |
| */ |
| try { |
| if (!mInCallCallbacks.lock.tryAcquire(2, 3, TimeUnit.SECONDS)) { |
| fail("Conference split failed"); |
| } |
| } catch (InterruptedException e) { |
| Log.i(TAG, "Test interrupted!"); |
| } |
| } |
| |
| MockConference verifyConferenceForOutgoingCall() { |
| try { |
| if (!connectionService.lock.tryAcquire(TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| TimeUnit.MILLISECONDS)) { |
| fail("No outgoing conference requested by Telecom"); |
| } |
| } catch (InterruptedException e) { |
| Log.i(TAG, "Test interrupted!"); |
| } |
| // Return the newly created conference object to the caller |
| MockConference conference = connectionService.conferences.get(0); |
| setAndVerifyConferenceForOutgoingCall(conference); |
| return conference; |
| } |
| |
| Pair<Conference, ConnectionRequest> verifyAdhocConferenceCall() { |
| try { |
| if (!connectionService.lock.tryAcquire(2, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| TimeUnit.MILLISECONDS)) { |
| fail("No conference requested by Telecom"); |
| } |
| } catch (InterruptedException e) { |
| Log.i(TAG, "Test interrupted!"); |
| } |
| return new Pair<>(connectionService.conferences.get(0), |
| connectionService.connectionRequest); |
| } |
| |
| void setAndVerifyConferenceForOutgoingCall(MockConference conference) { |
| conference.setActive(); |
| assertConferenceState(conference, Connection.STATE_ACTIVE); |
| } |
| |
| void verifyCallStateListener(int expectedCallState) throws InterruptedException { |
| mTestCallStateListener.getCountDownLatch().await( |
| TestUtils.WAIT_FOR_PHONE_STATE_LISTENER_CALLBACK_TIMEOUT_S, TimeUnit.SECONDS); |
| assertEquals(expectedCallState, mTestCallStateListener.getLastState()); |
| } |
| |
| void verifyPhoneStateListenerCallbacksForCall(int expectedCallState, String expectedNumber) |
| throws Exception { |
| assertTrue(mTelephonyCallback.mCallbackSemaphore.tryAcquire( |
| TestUtils.WAIT_FOR_PHONE_STATE_LISTENER_CALLBACK_TIMEOUT_S, TimeUnit.SECONDS)); |
| // At this point we can only be sure that we got AN update, but not necessarily the one we |
| // are looking for; wait until we see the state we want before verifying further. |
| waitUntilConditionIsTrueOrTimeout(new Condition() { |
| @Override |
| public Object expected() { |
| return true; |
| } |
| |
| @Override |
| public Object actual() { |
| return mTelephonyCallback.mCallStates |
| .stream() |
| .filter(p -> p == expectedCallState) |
| .count() > 0; |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Expected call state " + expectedCallState + " and number " |
| + expectedNumber); |
| |
| |
| // Get the most recent callback; it is possible that there was an initial state reported due |
| // to the fact that TelephonyManager will sometimes give an initial state back to the caller |
| // when the listener is registered. |
| int callState = mTelephonyCallback.mCallStates.get( |
| mTelephonyCallback.mCallStates.size() - 1); |
| assertEquals(expectedCallState, callState); |
| // Note: We do NOT check the phone number here. Due to changes in how the phone state |
| // broadcast is sent, the caller may receive multiple broadcasts, and the number will be |
| // present in one or the other. We waited for a full matching broadcast above so we can |
| // be sure the number was reported as expected. |
| } |
| |
| void verifyPhoneStateListenerCallbacksForEmergencyCall(String expectedNumber) |
| throws Exception { |
| assertTrue(mTelephonyCallback.mCallbackSemaphore.tryAcquire( |
| TestUtils.WAIT_FOR_PHONE_STATE_LISTENER_CALLBACK_TIMEOUT_S, TimeUnit.SECONDS)); |
| // At this point we can only be sure that we got AN update, but not necessarily the one we |
| // are looking for; wait until we see the state we want before verifying further. |
| waitUntilConditionIsTrueOrTimeout(new Condition() { |
| @Override |
| public Object expected() { |
| return true; |
| } |
| |
| @Override |
| public Object actual() { |
| return mTelephonyCallback |
| .mLastOutgoingEmergencyNumber != null |
| && mTelephonyCallback |
| .mLastOutgoingEmergencyNumber.getNumber() |
| .equals(expectedNumber); |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Expected emergency number: " + expectedNumber); |
| |
| assertEquals(mTelephonyCallback.mLastOutgoingEmergencyNumber.getNumber(), |
| expectedNumber); |
| } |
| |
| /** |
| * Disconnect the created test call and verify that Telecom has cleared all calls. |
| */ |
| void cleanupCalls() { |
| if (mInCallCallbacks != null && mInCallCallbacks.getService() != null) { |
| mInCallCallbacks.getService().disconnectAllConferenceCalls(); |
| mInCallCallbacks.getService().disconnectAllCalls(); |
| assertNumConferenceCalls(mInCallCallbacks.getService(), 0); |
| assertNumCalls(mInCallCallbacks.getService(), 0); |
| } |
| } |
| |
| /** |
| * Place a new outgoing call via the {@link CtsConnectionService} |
| */ |
| private void placeNewCallWithPhoneAccount(Bundle extras, int videoState) { |
| if (extras == null) { |
| extras = new Bundle(); |
| } |
| if (!extras.containsKey(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE)) { |
| extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, |
| TestUtils.TEST_PHONE_ACCOUNT_HANDLE); |
| } |
| |
| if (!VideoProfile.isAudioOnly(videoState)) { |
| extras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState); |
| } |
| Uri number; |
| if (extras.containsKey(TestUtils.EXTRA_PHONE_NUMBER)) { |
| number = extras.getParcelable(TestUtils.EXTRA_PHONE_NUMBER); |
| } else { |
| number = createTestNumber(); |
| } |
| mTelecomManager.placeCall(number, extras); |
| } |
| |
| /** |
| * Create a new number each time for a new test. Telecom has special logic to reuse certain |
| * calls if multiple calls to the same number are placed within a short period of time which |
| * can cause certain tests to fail. |
| */ |
| Uri createTestNumber() { |
| return Uri.fromParts("tel", String.valueOf(++sCounter), null); |
| } |
| |
| /** |
| * Creates a new random phone number in the range: |
| * 000-000-0000 |
| * to |
| * 999-999-9999 |
| * @return Randomized phone number. |
| */ |
| Uri createRandomTestNumber() { |
| return Uri.fromParts("tel", String.format("16%05d", new Random().nextInt(99999)) |
| + String.format("%04d", new Random().nextInt(9999)), null); |
| } |
| |
| public static Uri getTestNumber() { |
| return Uri.fromParts("tel", String.valueOf(sCounter), null); |
| } |
| |
| public boolean isLoggedCall(PhoneAccountHandle handle) { |
| PhoneAccount phoneAccount = mTelecomManager.getPhoneAccount(handle); |
| Bundle extras = phoneAccount.getExtras(); |
| if (extras == null) { |
| extras = new Bundle(); |
| } |
| boolean isSelfManaged = (phoneAccount.getCapabilities() |
| & PhoneAccount.CAPABILITY_SELF_MANAGED) == PhoneAccount.CAPABILITY_SELF_MANAGED; |
| // Calls are logged if: |
| // 1. They're not self-managed |
| // 2. They're self-managed and are configured to request logging. |
| return (!isSelfManaged |
| || (isSelfManaged |
| && extras.getBoolean(PhoneAccount.EXTRA_LOG_SELF_MANAGED_CALLS) |
| && (phoneAccount.getSupportedUriSchemes().contains(PhoneAccount.SCHEME_TEL) |
| || phoneAccount.getSupportedUriSchemes().contains(PhoneAccount.SCHEME_SIP)))); |
| } |
| |
| public CountDownLatch getCallLogEntryLatch() { |
| CountDownLatch changeLatch = new CountDownLatch(1); |
| mContext.getContentResolver().registerContentObserver( |
| CallLog.Calls.CONTENT_URI, true, |
| new ContentObserver(mHandler) { |
| @Override |
| public void onChange(boolean selfChange, Uri uri) { |
| mContext.getContentResolver().unregisterContentObserver(this); |
| changeLatch.countDown(); |
| super.onChange(selfChange); |
| } |
| }); |
| return changeLatch; |
| } |
| |
| |
| public void verifyCallLogging(CountDownLatch logLatch, boolean isCallLogged, Uri testNumber) { |
| Cursor logCursor = getLatestCallLogCursorIfMatchesUri(logLatch, isCallLogged, testNumber); |
| if (isCallLogged) { |
| assertNotNull("Call log entry not found for test number", logCursor); |
| } |
| } |
| |
| public void verifyCallLogging(Uri testNumber, int expectedLogType) { |
| CountDownLatch logLatch = getCallLogEntryLatch(); |
| Cursor logCursor = getLatestCallLogCursorIfMatchesUri(logLatch, true /*isCallLogged*/, |
| testNumber); |
| assertNotNull("Call log entry not found for test number", logCursor); |
| int typeIndex = logCursor.getColumnIndex(CallLog.Calls.TYPE); |
| int type = logCursor.getInt(typeIndex); |
| assertEquals("recorded type does not match expected", expectedLogType, type); |
| } |
| |
| public Cursor getLatestCallLogCursorIfMatchesUri(CountDownLatch latch, boolean newLogExpected, |
| Uri testNumber) { |
| if (newLogExpected) { |
| // Wait for the content observer to report that we have gotten a new call log entry. |
| try { |
| latch.await(WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, TimeUnit.MILLISECONDS); |
| } catch (InterruptedException ie) { |
| fail("Expected log latch"); |
| } |
| } |
| |
| // Query the latest entry into the call log. |
| Cursor callsCursor = mContext.getContentResolver().query(CallLog.Calls.CONTENT_URI, null, |
| null, null, CallLog.Calls._ID + " DESC limit 1;"); |
| int numberIndex = callsCursor.getColumnIndex(CallLog.Calls.NUMBER); |
| if (callsCursor.moveToNext()) { |
| String number = callsCursor.getString(numberIndex); |
| if (testNumber.getSchemeSpecificPart().equals(number)) { |
| return callsCursor; |
| } else { |
| // Last call log entry doesnt match expected number. |
| return null; |
| } |
| } |
| // No Calls |
| return null; |
| } |
| |
| void assertNumCalls(final MockInCallService inCallService, final int numCalls) { |
| waitUntilConditionIsTrueOrTimeout(new Condition() { |
| @Override |
| public Object expected() { |
| return numCalls; |
| } |
| @Override |
| public Object actual() { |
| return inCallService.getCallCount(); |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "InCallService should contain " + numCalls + " calls." |
| ); |
| } |
| |
| void assertNumConferenceCalls(final MockInCallService inCallService, final int numCalls) { |
| waitUntilConditionIsTrueOrTimeout(new Condition() { |
| @Override |
| public Object expected() { |
| return numCalls; |
| } |
| @Override |
| public Object actual() { |
| return inCallService.getConferenceCallCount(); |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "InCallService should contain " + numCalls + " conference calls." |
| ); |
| } |
| |
| void assertActiveCSConnections(final int numConnections) { |
| waitUntilConditionIsTrueOrTimeout(new Condition() { |
| @Override |
| public Object expected() { |
| return numConnections; |
| } |
| |
| @Override |
| public Object actual() { |
| return getNumberOfActiveConnections(); |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "ConnectionService should contain " + numConnections + " connections." |
| ); |
| } |
| |
| void assertCSConnections(final int numConnections) { |
| waitUntilConditionIsTrueOrTimeout(new Condition() { |
| @Override |
| public Object expected() { |
| return numConnections; |
| } |
| |
| @Override |
| public Object actual() { |
| return CtsConnectionService |
| .getAllConnectionsFromTelecom() |
| .size(); |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "ConnectionService should contain " + numConnections + " connections." |
| ); |
| } |
| |
| void assertNumConnections(final MockConnectionService connService, final int numConnections) { |
| waitUntilConditionIsTrueOrTimeout(new Condition() { |
| @Override |
| public Object expected() { |
| return numConnections; |
| } |
| @Override |
| public Object actual() { |
| return connService.getAllConnections().size(); |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "ConnectionService should contain " + numConnections + " connections." |
| ); |
| } |
| |
| void assertMuteState(final InCallService incallService, final boolean isMuted) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return isMuted; |
| } |
| |
| @Override |
| public Object actual() { |
| final CallAudioState state = incallService.getCallAudioState(); |
| return state == null ? null : state.isMuted(); |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Phone's mute state should be: " + isMuted |
| ); |
| } |
| |
| void assertMuteState(final MockConnection connection, final boolean isMuted) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return isMuted; |
| } |
| |
| @Override |
| public Object actual() { |
| final CallAudioState state = connection.getCallAudioState(); |
| return state == null ? null : state.isMuted(); |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Connection's mute state should be: " + isMuted |
| ); |
| } |
| |
| void assertAudioRoute(final InCallService incallService, final int route) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return route; |
| } |
| |
| @Override |
| public Object actual() { |
| final CallAudioState state = incallService.getCallAudioState(); |
| return state == null ? null : state.getRoute(); |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Phone's audio route should be: " + route |
| ); |
| } |
| |
| void assertNotAudioRoute(final InCallService incallService, final int route) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return new Boolean(true); |
| } |
| |
| @Override |
| public Object actual() { |
| final CallAudioState state = incallService.getCallAudioState(); |
| return route != state.getRoute(); |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Phone's audio route should not be: " + route |
| ); |
| } |
| |
| void assertAudioRoute(final MockConnection connection, final int route) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return route; |
| } |
| |
| @Override |
| public Object actual() { |
| final CallAudioState state = ((Connection) connection).getCallAudioState(); |
| return state == null ? null : state.getRoute(); |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Connection's audio route should be: " + route |
| ); |
| } |
| |
| void assertConnectionState(final Connection connection, final int state) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return state; |
| } |
| |
| @Override |
| public Object actual() { |
| return connection.getState(); |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Connection should be in state " + state |
| ); |
| } |
| |
| void assertCallState(final Call call, final int state) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return true; |
| } |
| |
| @Override |
| public Object actual() { |
| return call.getState() == state && call.getDetails().getState() == state; |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Expected state: " + state + ", callState=" + call.getState() + ", detailState=" |
| + call.getDetails().getState() |
| ); |
| } |
| |
| void assertCallConferenceableList(final Call call, final List<Call> conferenceableList) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return conferenceableList; |
| } |
| |
| @Override |
| public Object actual() { |
| return call.getConferenceableCalls(); |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Call: " + call + " does not have the correct conferenceable call list." |
| ); |
| } |
| |
| void assertDtmfString(final MockConnection connection, final String dtmfString) { |
| waitUntilConditionIsTrueOrTimeout(new Condition() { |
| @Override |
| public Object expected() { |
| return dtmfString; |
| } |
| |
| @Override |
| public Object actual() { |
| return connection.getDtmfString(); |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "DTMF string should be equivalent to entered DTMF characters: " + dtmfString |
| ); |
| } |
| |
| void assertDtmfString(final MockConference conference, final String dtmfString) { |
| waitUntilConditionIsTrueOrTimeout(new Condition() { |
| @Override |
| public Object expected() { |
| return dtmfString; |
| } |
| |
| @Override |
| public Object actual() { |
| return conference.getDtmfString(); |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "DTMF string should be equivalent to entered DTMF characters: " + dtmfString |
| ); |
| } |
| |
| void assertCallDisplayName(final Call call, final String name) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return name; |
| } |
| |
| @Override |
| public Object actual() { |
| return call.getDetails().getCallerDisplayName(); |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Call should have display name: " + name |
| ); |
| } |
| |
| void assertCallHandle(final Call call, final Uri expectedHandle) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return expectedHandle; |
| } |
| |
| @Override |
| public Object actual() { |
| return call.getDetails().getHandle(); |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Call should have handle name: " + expectedHandle |
| ); |
| } |
| |
| void assertCallConnectTimeChanged(final Call call, final long time) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return true; |
| } |
| |
| @Override |
| public Object actual() { |
| return call.getDetails().getConnectTimeMillis() != time; |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Call have connect time: " + time |
| ); |
| } |
| |
| void assertConnectionCallDisplayName(final Connection connection, final String name) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return name; |
| } |
| |
| @Override |
| public Object actual() { |
| return connection.getCallerDisplayName(); |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Connection should have display name: " + name |
| ); |
| } |
| |
| void assertDisconnectReason(final Connection connection, final String disconnectReason) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return disconnectReason; |
| } |
| |
| @Override |
| public Object actual() { |
| return connection.getDisconnectCause().getReason(); |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Connection should have been disconnected with reason: " + disconnectReason |
| ); |
| } |
| |
| void assertConferenceState(final Conference conference, final int state) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return state; |
| } |
| |
| @Override |
| public Object actual() { |
| return conference.getState(); |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Conference should be in state " + state |
| ); |
| } |
| |
| |
| void assertOutgoingCallBroadcastReceived(boolean received) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return received; |
| } |
| |
| @Override |
| public Object actual() { |
| return NewOutgoingCallBroadcastReceiver |
| .isNewOutgoingCallBroadcastReceived(); |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| received ? "Outgoing Call Broadcast should be received" |
| : "Outgoing Call Broadcast should not be received" |
| ); |
| } |
| |
| void assertCallDetailsConstructed(Call mCall, boolean constructed) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return constructed; |
| } |
| |
| @Override |
| public Object actual() { |
| return mCall != null && mCall.getDetails() != null; |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| constructed ? "Call Details should be constructed" |
| : "Call Details should not be constructed" |
| ); |
| } |
| |
| void assertCallGatewayConstructed(Call mCall, boolean constructed) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return constructed; |
| } |
| |
| @Override |
| public Object actual() { |
| return mCall != null && mCall.getDetails() != null |
| && mCall.getDetails().getGatewayInfo() != null; |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| constructed ? "Call Gateway should be constructed" |
| : "Call Gateway should not be constructed" |
| ); |
| } |
| |
| void assertCallNotNull(Call mCall, boolean notNull) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return notNull; |
| } |
| |
| @Override |
| public Object actual() { |
| return mCall != null; |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| notNull ? "Call should not be null" : "Call should be null" |
| ); |
| } |
| |
| /** |
| * Checks all fields of two PhoneAccounts for equality, with the exception of the enabled state. |
| * Should only be called after assertPhoneAccountRegistered when it can be guaranteed |
| * that the PhoneAccount is registered. |
| * @param expected The expected PhoneAccount. |
| * @param actual The actual PhoneAccount. |
| */ |
| void assertPhoneAccountEquals(final PhoneAccount expected, |
| final PhoneAccount actual) { |
| assertEquals(expected.getAddress(), actual.getAddress()); |
| assertEquals(expected.getAccountHandle(), actual.getAccountHandle()); |
| assertEquals(expected.getCapabilities(), actual.getCapabilities()); |
| assertTrue(areBundlesEqual(expected.getExtras(), actual.getExtras())); |
| assertEquals(expected.getHighlightColor(), actual.getHighlightColor()); |
| assertEquals(expected.getIcon(), actual.getIcon()); |
| assertEquals(expected.getLabel(), actual.getLabel()); |
| assertEquals(expected.getShortDescription(), actual.getShortDescription()); |
| assertEquals(expected.getSubscriptionAddress(), actual.getSubscriptionAddress()); |
| assertEquals(expected.getSupportedUriSchemes(), actual.getSupportedUriSchemes()); |
| } |
| |
| void assertPhoneAccountRegistered(final PhoneAccountHandle handle) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return true; |
| } |
| |
| @Override |
| public Object actual() { |
| return mTelecomManager.getPhoneAccount(handle) != null; |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Phone account registration failed for " + handle |
| ); |
| } |
| |
| void assertPhoneAccountEnabled(final PhoneAccountHandle handle) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return true; |
| } |
| |
| @Override |
| public Object actual() { |
| PhoneAccount phoneAccount = mTelecomManager.getPhoneAccount(handle); |
| return (phoneAccount != null && phoneAccount.isEnabled()); |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Phone account enable failed for " + handle |
| ); |
| } |
| |
| void assertPhoneAccountIsDefault(final PhoneAccountHandle handle) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return true; |
| } |
| |
| @Override |
| public Object actual() { |
| PhoneAccountHandle phoneAccountHandle = |
| mTelecomManager.getUserSelectedOutgoingPhoneAccount(); |
| return (phoneAccountHandle != null && phoneAccountHandle.equals(handle)); |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Failed to set default phone account to " + handle |
| ); |
| } |
| |
| void assertCtsConnectionServiceUnbound() { |
| if (CtsConnectionService.isBound()) { |
| assertTrue("CtsConnectionService not yet unbound!", |
| CtsConnectionService.waitForUnBinding()); |
| } |
| } |
| |
| void assertMockInCallServiceUnbound() { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return false; |
| } |
| |
| @Override |
| public Object actual() { |
| return MockInCallService.isServiceBound(); |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "MockInCallService not yet unbound!" |
| ); |
| } |
| |
| void assertIsOutgoingCallPermitted(boolean isPermitted, PhoneAccountHandle handle) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return isPermitted; |
| } |
| |
| @Override |
| public Object actual() { |
| return mTelecomManager.isOutgoingCallPermitted(handle); |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Expected isOutgoingCallPermitted to be " + isPermitted |
| ); |
| } |
| |
| void assertIsIncomingCallPermitted(boolean isPermitted, PhoneAccountHandle handle) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return isPermitted; |
| } |
| |
| @Override |
| public Object actual() { |
| return mTelecomManager.isIncomingCallPermitted(handle); |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Expected isIncomingCallPermitted to be " + isPermitted |
| ); |
| } |
| |
| void assertIsInCall(boolean isIncall) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return isIncall; |
| } |
| |
| @Override |
| public Object actual() { |
| return mTelecomManager.isInCall(); |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Expected isInCall to be " + isIncall |
| ); |
| } |
| |
| void assertIsInManagedCall(boolean isIncall) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return isIncall; |
| } |
| |
| @Override |
| public Object actual() { |
| return mTelecomManager.isInManagedCall(); |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Expected isInManagedCall to be " + isIncall |
| ); |
| } |
| |
| /** |
| * Asserts that a call's properties are as expected. |
| * |
| * @param call The call. |
| * @param properties The expected properties. |
| */ |
| public void assertCallProperties(final Call call, final int properties) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return true; |
| } |
| |
| @Override |
| public Object actual() { |
| return call.getDetails().hasProperty(properties); |
| } |
| }, |
| TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Call should have properties " + properties |
| ); |
| } |
| |
| /** |
| * Asserts that a call does not have any of the specified call capability bits specified. |
| * |
| * @param call The call. |
| * @param capabilities The capability or capabilities which are not expected. |
| */ |
| public void assertDoesNotHaveCallCapabilities(final Call call, final int capabilities) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return true; |
| } |
| |
| @Override |
| public Object actual() { |
| int callCapabilities = call.getDetails().getCallCapabilities(); |
| return !Call.Details.hasProperty(callCapabilities, capabilities); |
| } |
| }, |
| TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Call should not have capabilities " + capabilities |
| ); |
| } |
| |
| /** |
| * Asserts that a call does not have any of the specified call property bits specified. |
| * |
| * @param call The call. |
| * @param properties The property or properties which are not expected. |
| */ |
| public void assertDoesNotHaveCallProperties(final Call call, final int properties) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return true; |
| } |
| |
| @Override |
| public Object actual() { |
| return !call.getDetails().hasProperty(properties); |
| } |
| }, |
| TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Call should not have properties " + properties |
| ); |
| } |
| |
| /** |
| * Asserts that the audio manager reports the specified audio mode. |
| * |
| * @param audioManager The audio manager to check. |
| * @param expectedMode The expected audio mode. |
| */ |
| public void assertAudioMode(final AudioManager audioManager, final int expectedMode) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return true; |
| } |
| |
| @Override |
| public Object actual() { |
| return audioManager.getMode() == expectedMode; |
| } |
| }, |
| TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Audio mode was expected to be " + expectedMode |
| ); |
| } |
| |
| /** |
| * Asserts that a call's capabilities are as expected. |
| * |
| * @param call The call. |
| * @param capabilities The expected capabiltiies. |
| */ |
| public void assertCallCapabilities(final Call call, final int capabilities) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return true; |
| } |
| |
| @Override |
| public Object actual() { |
| return (call.getDetails().getCallCapabilities() & capabilities) == |
| capabilities; |
| } |
| }, |
| TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Call should have properties " + capabilities |
| ); |
| } |
| |
| MockInCallService getInCallService() { |
| return (mInCallCallbacks == null) ? null : mInCallCallbacks.getService(); |
| } |
| |
| /** |
| * Asserts that the {@link UiModeManager} mode matches the specified mode. |
| * |
| * @param uiMode The expected ui mode. |
| */ |
| public void assertUiMode(final int uiMode) { |
| waitUntilConditionIsTrueOrTimeout( |
| new Condition() { |
| @Override |
| public Object expected() { |
| return uiMode; |
| } |
| |
| @Override |
| public Object actual() { |
| return mUiModeManager.getCurrentModeType(); |
| } |
| }, |
| TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Expected ui mode " + uiMode |
| ); |
| } |
| |
| void waitUntilConditionIsTrueOrTimeout(Condition condition, long timeout, |
| String description) { |
| final long start = System.currentTimeMillis(); |
| while (!Objects.equals(condition.expected(), condition.actual()) |
| && System.currentTimeMillis() - start < timeout) { |
| sleep(50); |
| } |
| assertEquals(description, condition.expected(), condition.actual()); |
| } |
| |
| /** |
| * Performs some work, and waits for the condition to be met. If the condition is not met in |
| * each step of the loop, the work is performed again. |
| * |
| * @param work The work to perform. |
| * @param condition The condition. |
| * @param timeout The timeout. |
| * @param description Description of the work being performed. |
| */ |
| void doWorkAndWaitUntilConditionIsTrueOrTimeout(Work work, Condition condition, long timeout, |
| String description) { |
| final long start = System.currentTimeMillis(); |
| work.doWork(); |
| while (!condition.expected().equals(condition.actual()) |
| && System.currentTimeMillis() - start < timeout) { |
| sleep(50); |
| work.doWork(); |
| } |
| assertEquals(description, condition.expected(), condition.actual()); |
| } |
| |
| protected interface Condition { |
| Object expected(); |
| Object actual(); |
| } |
| |
| protected interface Work { |
| void doWork(); |
| } |
| |
| public static boolean areBundlesEqual(Bundle extras, Bundle newExtras) { |
| if (extras == null || newExtras == null) { |
| return extras == newExtras; |
| } |
| |
| if (extras.size() != newExtras.size()) { |
| return false; |
| } |
| |
| for (String key : extras.keySet()) { |
| if (key != null) { |
| final Object value = extras.get(key); |
| final Object newValue = newExtras.get(key); |
| if (!Objects.equals(value, newValue)) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| } |