blob: 1a9d8d7cf8b25b55e645e15d7b0384404e401f6f [file] [log] [blame]
/*
* 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 android.telecom.cts.MockConnectionService.ConnectionServiceCallbacks;
import android.telecom.cts.MockInCallService.InCallServiceCallbacks;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.telecom.CallAudioState;
import android.telecom.Call;
import android.telecom.Connection;
import android.telecom.ConnectionRequest;
import android.telecom.ConnectionService;
import android.telecom.InCallService;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.test.InstrumentationTestCase;
import android.text.TextUtils;
import android.util.Log;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
/**
* Extended suite of tests that use {@MockConnectionService} and {@MockInCallService} to verify
* the functionality of the Telecom service. Requires that the version of GmsCore installed on the
* device has the REGISTER_CALL_PROVIDER permission.
*/
public class ExtendedInCallServiceTest extends InstrumentationTestCase {
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, LABEL)
.setAddress(Uri.parse("tel:555-TEST"))
.setSubscriptionAddress(Uri.parse("tel:555-TEST"))
.setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
.setHighlightColor(Color.RED)
.setShortDescription(LABEL)
.setSupportedUriSchemes(Arrays.asList("tel"))
.build();
private Context mContext;
private TelecomManager mTelecomManager;
private InCallServiceCallbacks mInCallCallbacks;
private ConnectionServiceCallbacks mConnectionCallbacks;
private String mPreviousDefaultDialer = null;
private static int sCounter = 0;
@Override
protected void setUp() throws Exception {
super.setUp();
mContext = getInstrumentation().getContext();
mTelecomManager = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
if (shouldTestTelecom(mContext)) {
mTelecomManager.registerPhoneAccount(TEST_PHONE_ACCOUNT);
TestUtils.enablePhoneAccount(getInstrumentation(), TEST_PHONE_ACCOUNT_HANDLE);
mPreviousDefaultDialer = TestUtils.getDefaultDialer(getInstrumentation());
TestUtils.setDefaultDialer(getInstrumentation(), PACKAGE);
setupCallbacks();
placeAndVerifyCall();
verifyConnectionService();
}
}
@Override
protected void tearDown() throws Exception {
if (shouldTestTelecom(mContext)) {
if (mInCallCallbacks != null && mInCallCallbacks.getService() != null) {
mInCallCallbacks.getService().disconnectLastCall();
assertNumCalls(mInCallCallbacks.getService(), 0);
}
if (!TextUtils.isEmpty(mPreviousDefaultDialer)) {
TestUtils.setDefaultDialer(getInstrumentation(), mPreviousDefaultDialer);
}
mTelecomManager.unregisterPhoneAccount(TEST_PHONE_ACCOUNT_HANDLE);
}
super.tearDown();
}
public void testWithMockConnection_AddNewOutgoingCallAndThenDisconnect() {
if (!shouldTestTelecom(mContext)) {
return;
}
final MockInCallService inCallService = mInCallCallbacks.getService();
inCallService.disconnectLastCall();
assertNumCalls(inCallService, 0);
}
public void testWithMockConnection_MuteAndUnmutePhone() {
if (!shouldTestTelecom(mContext)) {
return;
}
final MockInCallService inCallService = mInCallCallbacks.getService();
final Call call = inCallService.getLastCall();
final MockConnection connection = mConnectionCallbacks.outgoingConnection;
assertCallState(call, Call.STATE_ACTIVE);
assertMuteState(connection, false);
inCallService.setMuted(true);;
assertMuteState(connection, true);
assertMuteState(inCallService, true);
inCallService.setMuted(false);
assertMuteState(connection, false);
assertMuteState(inCallService, false);
}
public void testWithMockConnection_SwitchAudioRoutes() {
if (!shouldTestTelecom(mContext)) {
return;
}
final MockInCallService inCallService = mInCallCallbacks.getService();
final MockConnection connection = mConnectionCallbacks.outgoingConnection;
final Call call = inCallService.getLastCall();
assertCallState(call, Call.STATE_ACTIVE);
// Only test speaker and earpiece modes because the other modes are dependent on having
// a bluetooth headset or wired headset connected.
inCallService.setAudioRoute(CallAudioState.ROUTE_SPEAKER);
assertAudioRoute(connection, CallAudioState.ROUTE_SPEAKER);
assertAudioRoute(inCallService, CallAudioState.ROUTE_SPEAKER);
inCallService.setAudioRoute(CallAudioState.ROUTE_EARPIECE);
assertAudioRoute(connection, CallAudioState.ROUTE_EARPIECE);
assertAudioRoute(inCallService, CallAudioState.ROUTE_EARPIECE);
}
/**
* Tests that DTMF Tones are sent from the {@link InCallService} to the
* {@link ConnectionService} in the correct sequence.
*/
public void testWithMockConnection_DtmfTones() {
if (!shouldTestTelecom(mContext)) {
return;
}
final MockInCallService inCallService = mInCallCallbacks.getService();
final MockConnection connection = mConnectionCallbacks.outgoingConnection;
final Call call = inCallService.getLastCall();
assertCallState(call, Call.STATE_ACTIVE);
assertDtmfString(connection, "");
call.playDtmfTone('1');
assertDtmfString(connection, "1");
call.playDtmfTone('2');
assertDtmfString(connection, "12");
call.playDtmfTone('3');
call.playDtmfTone('4');
call.playDtmfTone('5');
assertDtmfString(connection, "12345");
}
public void testWithMockConnection_HoldAndUnholdCall() {
if (!shouldTestTelecom(mContext)) {
return;
}
final MockInCallService inCallService = mInCallCallbacks.getService();
final MockConnection connection = mConnectionCallbacks.outgoingConnection;
final Call call = inCallService.getLastCall();
assertCallState(call, Call.STATE_ACTIVE);
call.hold();
assertCallState(call, Call.STATE_HOLDING);
assertEquals(Connection.STATE_HOLDING, connection.getState());
call.unhold();
assertCallState(call, Call.STATE_ACTIVE);
assertEquals(Connection.STATE_ACTIVE, connection.getState());
}
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) {
this.lock.release();
}
};
MockInCallService.setCallbacks(mInCallCallbacks);
mConnectionCallbacks = new ConnectionServiceCallbacks() {
@Override
public void onCreateOutgoingConnection(MockConnection connection,
ConnectionRequest request) {
this.lock.release();
}
};
MockConnectionService.setCallbacks(mConnectionCallbacks);
}
/**
* Puts Telecom in a state where there is an active call provided by the
* {@link MockConnectionService} which can be tested.
*/
private void placeAndVerifyCall() {
placeNewCallWithPhoneAccount();
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 call after adding a call.", 1,
mInCallCallbacks.getService().getCallCount());
assertTrue("TelecomManager should be in a call", mTelecomManager.isInCall());
}
private void verifyConnectionService() {
try {
if (!mConnectionCallbacks.lock.tryAcquire(3, TimeUnit.SECONDS)) {
fail("No outgoing call connection requested by Telecom");
}
} catch (InterruptedException e) {
Log.i(TAG, "Test interrupted!");
}
assertNotNull("Telecom should bind to and create ConnectionService",
mConnectionCallbacks.getService());
assertNotNull("Telecom should create outgoing connection for outgoing call",
mConnectionCallbacks.outgoingConnection);
assertNull("Telecom should not create incoming connection for outgoing call",
mConnectionCallbacks.incomingConnection);
final MockConnection connection = mConnectionCallbacks.outgoingConnection;
connection.setDialing();
connection.setActive();
assertEquals(Connection.STATE_ACTIVE, connection.getState());
}
/**
* Place a new outgoing call via the {@link MockConnectionService}
*/
private void placeNewCallWithPhoneAccount() {
final Intent intent = new Intent(Intent.ACTION_CALL, getTestNumber());
intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEST_PHONE_ACCOUNT_HANDLE);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
/**
* 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.
*/
private Uri getTestNumber() {
return Uri.fromParts("tel", String.valueOf(sCounter++), null);
}
private 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."
);
}
private void assertMuteState(final InCallService incallService, final boolean isMuted) {
waitUntilConditionIsTrueOrTimeout(
new Condition() {
@Override
public Object expected() {
return isMuted;
}
@Override
public Object actual() {
return incallService.getCallAudioState().isMuted();
}
},
WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
"Phone's mute state should be: " + isMuted
);
}
private void assertMuteState(final MockConnection connection, final boolean isMuted) {
waitUntilConditionIsTrueOrTimeout(
new Condition() {
@Override
public Object expected() {
return isMuted;
}
@Override
public Object actual() {
return connection.getCallAudioState().isMuted();
}
},
WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
"Connection's mute state should be: " + isMuted
);
}
private void assertAudioRoute(final InCallService incallService, final int route) {
waitUntilConditionIsTrueOrTimeout(
new Condition() {
@Override
public Object expected() {
return route;
}
@Override
public Object actual() {
return incallService.getCallAudioState().getRoute();
}
},
WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
"Phone's audio route should be: " + route
);
}
private void assertAudioRoute(final MockConnection connection, final int route) {
waitUntilConditionIsTrueOrTimeout(
new Condition() {
@Override
public Object expected() {
return route;
}
@Override
public Object actual() {
return connection.getCallAudioState().getRoute();
}
},
WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
"Connection's audio route should be: " + route
);
}
private 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
);
}
private 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
);
}
private 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());
}
private interface Condition {
Object expected();
Object actual();
}
}