| /* |
| * 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.*; |
| |
| import static org.hamcrest.CoreMatchers.not; |
| import static org.hamcrest.CoreMatchers.equalTo; |
| import static org.junit.Assert.assertThat; |
| |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.graphics.Color; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.SystemClock; |
| import android.telecom.Call; |
| import android.telecom.CallAudioState; |
| import android.telecom.Conference; |
| import android.telecom.Connection; |
| 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.test.InstrumentationTestCase; |
| import android.text.TextUtils; |
| import android.util.Log; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.concurrent.TimeUnit; |
| |
| /** |
| * 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 PhoneAccountHandle TEST_PHONE_ACCOUNT_HANDLE = |
| new PhoneAccountHandle(new ComponentName(PACKAGE, COMPONENT), ACCOUNT_ID); |
| |
| public static final PhoneAccount TEST_PHONE_ACCOUNT = PhoneAccount.builder( |
| TEST_PHONE_ACCOUNT_HANDLE, ACCOUNT_LABEL) |
| .setAddress(Uri.parse("tel:555-TEST")) |
| .setSubscriptionAddress(Uri.parse("tel:555-TEST")) |
| .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER | |
| PhoneAccount.CAPABILITY_VIDEO_CALLING | |
| PhoneAccount.CAPABILITY_CONNECTION_MANAGER) |
| .setHighlightColor(Color.RED) |
| .setShortDescription(ACCOUNT_LABEL) |
| .addSupportedUriScheme(PhoneAccount.SCHEME_TEL) |
| .addSupportedUriScheme(PhoneAccount.SCHEME_VOICEMAIL) |
| .build(); |
| |
| private static int sCounter = 9999; |
| |
| Context mContext; |
| TelecomManager mTelecomManager; |
| |
| InvokeCounter mOnBringToForegroundCounter; |
| InvokeCounter mOnCallAudioStateChangedCounter; |
| InvokeCounter mOnPostDialWaitCounter; |
| InvokeCounter mOnCannedTextResponsesLoadedCounter; |
| |
| InCallServiceCallbacks mInCallCallbacks; |
| String mPreviousDefaultDialer = null; |
| MockConnectionService connectionService = null; |
| |
| boolean mShouldTestTelecom = true; |
| |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| mContext = getInstrumentation().getContext(); |
| mTelecomManager = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE); |
| |
| mShouldTestTelecom = shouldTestTelecom(mContext); |
| if (mShouldTestTelecom) { |
| mPreviousDefaultDialer = TestUtils.getDefaultDialer(getInstrumentation()); |
| TestUtils.setDefaultDialer(getInstrumentation(), PACKAGE); |
| setupCallbacks(); |
| } |
| } |
| |
| @Override |
| protected void tearDown() throws Exception { |
| if (mShouldTestTelecom) { |
| cleanupCalls(); |
| if (!TextUtils.isEmpty(mPreviousDefaultDialer)) { |
| TestUtils.setDefaultDialer(getInstrumentation(), mPreviousDefaultDialer); |
| } |
| tearDownConnectionService(TEST_PHONE_ACCOUNT_HANDLE); |
| } |
| super.tearDown(); |
| } |
| |
| protected PhoneAccount setupConnectionService(MockConnectionService connectionService, |
| int flags) |
| throws Exception { |
| if (connectionService != null) { |
| this.connectionService = connectionService; |
| } else { |
| // Generate a vanilla mock connection service, if not provided. |
| this.connectionService = new MockConnectionService(); |
| } |
| CtsConnectionService.setUp(TEST_PHONE_ACCOUNT_HANDLE, this.connectionService); |
| |
| if ((flags & FLAG_REGISTER) != 0) { |
| mTelecomManager.registerPhoneAccount(TEST_PHONE_ACCOUNT); |
| } |
| if ((flags & FLAG_ENABLE) != 0) { |
| TestUtils.enablePhoneAccount(getInstrumentation(), TEST_PHONE_ACCOUNT_HANDLE); |
| } |
| |
| return TEST_PHONE_ACCOUNT; |
| } |
| |
| protected void tearDownConnectionService(PhoneAccountHandle accountHandle) throws Exception { |
| mTelecomManager.unregisterPhoneAccount(accountHandle); |
| CtsConnectionService.tearDown(); |
| this.connectionService = null; |
| } |
| |
| 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); |
| } |
| |
| private 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(); |
| } |
| @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); |
| } |
| @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); |
| } |
| }; |
| |
| MockInCallService.setCallbacks(mInCallCallbacks); |
| |
| // TODO: If more InvokeCounters are added in the future, consider consolidating them into a |
| // single Collection. |
| mOnBringToForegroundCounter = new InvokeCounter("OnBringToForeground"); |
| mOnCallAudioStateChangedCounter = new InvokeCounter("OnCallAudioStateChanged"); |
| mOnPostDialWaitCounter = new InvokeCounter("OnPostDialWait"); |
| mOnCannedTextResponsesLoadedCounter = new InvokeCounter("OnCannedTextResponsesLoaded"); |
| } |
| |
| /** |
| * 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) { |
| 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(TEST_PHONE_ACCOUNT_HANDLE, extras); |
| |
| try { |
| if (!mInCallCallbacks.lock.tryAcquire(3, 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); |
| } |
| |
| /** |
| * 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) { |
| assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits()); |
| int currentCallCount = 0; |
| if (mInCallCallbacks.getService() != null) { |
| currentCallCount = mInCallCallbacks.getService().getCallCount(); |
| } |
| placeNewCallWithPhoneAccount(extras, videoState); |
| |
| try { |
| if (!mInCallCallbacks.lock.tryAcquire(3, 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()); |
| } |
| |
| 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 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; |
| } |
| |
| void setAndVerifyConnectionForIncomingCall(MockConnection connection) { |
| 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; |
| } |
| |
| void setAndVerifyConferenceForOutgoingCall(MockConference conference) { |
| conference.setActive(); |
| assertConferenceState(conference, Connection.STATE_ACTIVE); |
| } |
| |
| /** |
| * 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(); |
| } |
| extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEST_PHONE_ACCOUNT_HANDLE); |
| |
| if (!VideoProfile.isAudioOnly(videoState)) { |
| extras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState); |
| } |
| |
| mTelecomManager.placeCall(createTestNumber(), 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); |
| } |
| |
| public static Uri getTestNumber() { |
| return Uri.fromParts("tel", String.valueOf(sCounter), 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 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 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 state; |
| } |
| |
| @Override |
| public Object actual() { |
| return call.getState(); |
| } |
| }, |
| WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, |
| "Call should be in state " + state |
| ); |
| } |
| |
| 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 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 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 waitUntilConditionIsTrueOrTimeout(Condition condition, long timeout, |
| String description) { |
| final long start = System.currentTimeMillis(); |
| while (!condition.expected().equals(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(); |
| } |
| |
| /** |
| * Utility class used to track the number of times a callback was invoked, and the arguments it |
| * was invoked with. This class is prefixed Invoke rather than the more typical Call for |
| * disambiguation purposes. |
| */ |
| protected static final class InvokeCounter { |
| private final String mName; |
| private final Object mLock = new Object(); |
| private final ArrayList<Object[]> mInvokeArgs = new ArrayList<>(); |
| |
| private int mInvokeCount; |
| |
| public InvokeCounter(String callbackName) { |
| mName = callbackName; |
| } |
| |
| public void invoke(Object... args) { |
| synchronized (mLock) { |
| mInvokeCount++; |
| mInvokeArgs.add(args); |
| mLock.notifyAll(); |
| } |
| } |
| |
| public Object[] getArgs(int index) { |
| synchronized (mLock) { |
| return mInvokeArgs.get(index); |
| } |
| } |
| |
| public int getInvokeCount() { |
| synchronized (mLock) { |
| return mInvokeCount; |
| } |
| } |
| |
| public void waitForCount(int count) { |
| waitForCount(count, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS); |
| } |
| |
| public void waitForCount(int count, long timeoutMillis) { |
| waitForCount(count, timeoutMillis, null); |
| } |
| |
| public void waitForCount(int count, long timeoutMillis, String message) { |
| synchronized (mLock) { |
| final long startTimeMillis = SystemClock.uptimeMillis(); |
| while (mInvokeCount < count) { |
| try { |
| final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; |
| final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis; |
| if (remainingTimeMillis <= 0) { |
| if (message != null) { |
| fail(message); |
| } else { |
| fail(String.format("Expected %s to be called %d times.", mName, |
| count)); |
| } |
| } |
| mLock.wait(timeoutMillis); |
| } catch (InterruptedException ie) { |
| /* ignore */ |
| } |
| } |
| } |
| } |
| } |
| } |