blob: a613f2b6cd2fd1fb1ce8fdd1dc54527a98f2e64e [file] [log] [blame]
/*
* Copyright (C) 2022 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.telephony.ims.cts;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.telecom.Call;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.cts.InCallServiceStateValidator;
import android.telephony.cts.InCallServiceStateValidator.InCallServiceCallbacks;
import android.telephony.cts.util.TelephonyUtils;
import android.telephony.ims.feature.ImsFeature;
import android.telephony.ims.feature.MmTelFeature;
import android.telephony.ims.stub.ImsFeatureConfiguration;
import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.ShellIdentityUtils;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Base class for ImsCall test.
*/
public class ImsCallingBase {
protected static ImsServiceConnector sServiceConnector;
private static final String LOG_TAG = "ImsCallingBase";
protected static final String PACKAGE = "android.telephony.ims.cts";
protected static final String PACKAGE_CTS_DIALER = "android.telephony.cts";
protected static final String COMMAND_SET_DEFAULT_DIALER = "telecom set-default-dialer ";
protected static final String COMMAND_GET_DEFAULT_DIALER = "telecom get-default-dialer";
// The timeout to wait in current state in milliseconds
protected static final int WAIT_IN_CURRENT_STATE = 100;
public static final int WAIT_FOR_SERVICE_TO_UNBOUND = 40000;
public static final int WAIT_FOR_CONDITION = 3000;
public static final int WAIT_FOR_CALL_STATE = 10000;
public static final int WAIT_FOR_CALL_STATE_ACTIVE = 15000;
public static final int LATCH_INCALL_SERVICE_BOUND = 1;
public static final int LATCH_INCALL_SERVICE_UNBOUND = 2;
public static final int LATCH_IS_ON_CALL_ADDED = 3;
public static final int LATCH_IS_ON_CALL_REMOVED = 4;
public static final int LATCH_IS_CALL_DIALING = 5;
public static final int LATCH_IS_CALL_ACTIVE = 6;
public static final int LATCH_IS_CALL_DISCONNECTING = 7;
public static final int LATCH_IS_CALL_DISCONNECTED = 8;
public static final int LATCH_IS_CALL_RINGING = 9;
public static final int LATCH_IS_CALL_HOLDING = 10;
public static final int LATCH_IS_ON_CALL_REMOTELY_HELD = 11;
public static final int LATCH_IS_ON_CALL_REMOTELY_UNHELD = 12;
public static final int LATCH_MAX = 13;
public static final int TEST_RTP_THRESHOLD_PACKET_LOSS_RATE = 47;
public static final int TEST_RTP_THRESHOLD_JITTER_MILLIS = 150;
public static final long TEST_RTP_THRESHOLD_INACTIVITY_TIME_MILLIS = 3000;
protected static boolean sIsBound = false;
protected static int sCounter = 5553639;
protected static int sTestSlot = 0;
protected static int sTestSub = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
protected static long sPreviousOptInStatus = 0;
protected static long sPreviousEn4GMode = 0;
protected static String sPreviousDefaultDialer;
private static CarrierConfigReceiver sReceiver;
private static SubscriptionManager sSubscriptionManager;
protected int mParticipantCount = 0;
protected final Object mLock = new Object();
protected InCallServiceCallbacks mServiceCallBack;
protected Context mContext;
protected ConcurrentHashMap<String, Call> mCalls = new ConcurrentHashMap<String, Call>();
protected String mCurrentCallId = null;
protected static final CountDownLatch[] sLatches = new CountDownLatch[LATCH_MAX];
protected static void initializeLatches() {
for (int i = 0; i < LATCH_MAX; i++) {
sLatches[i] = new CountDownLatch(1);
}
}
public boolean callingTestLatchCountdown(int latchIndex, int waitMs) {
boolean complete = false;
try {
CountDownLatch latch;
synchronized (mLock) {
latch = sLatches[latchIndex];
}
complete = latch.await(waitMs, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
//complete == false
}
synchronized (mLock) {
sLatches[latchIndex] = new CountDownLatch(1);
}
return complete;
}
public void countDownLatch(int latchIndex) {
synchronized (mLock) {
sLatches[latchIndex].countDown();
}
}
protected abstract static class BaseReceiver extends BroadcastReceiver {
protected CountDownLatch mLatch = new CountDownLatch(1);
void clearQueue() {
mLatch = new CountDownLatch(1);
}
void waitForChanged() throws Exception {
mLatch.await(5000, TimeUnit.MILLISECONDS);
}
}
protected static class CarrierConfigReceiver extends BaseReceiver {
private final int mSubId;
CarrierConfigReceiver(int subId) {
mSubId = subId;
}
@Override
public void onReceive(Context context, Intent intent) {
if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
int subId = intent.getIntExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, -1);
if (mSubId == subId) {
mLatch.countDown();
}
}
}
}
public interface Condition {
Object expected();
Object actual();
}
protected void sleep(long ms) {
try {
Thread.sleep(ms);
} catch (Exception e) {
Log.d(LOG_TAG, "InterruptedException");
}
}
protected 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());
}
public static void beforeAllTestsBase() throws Exception {
sServiceConnector = new ImsServiceConnector(InstrumentationRegistry.getInstrumentation());
// Remove all live ImsServices until after these tests are done
sServiceConnector.clearAllActiveImsServices(sTestSlot);
sReceiver = new CarrierConfigReceiver(sTestSub);
IntentFilter filter = new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
// ACTION_CARRIER_CONFIG_CHANGED is sticky, so we will get a callback right away.
InstrumentationRegistry.getInstrumentation().getContext()
.registerReceiver(sReceiver, filter);
UiAutomation ui = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
ui.adoptShellPermissionIdentity();
// Get the default dialer and save it to restore after test ends.
sPreviousDefaultDialer = getDefaultDialer(InstrumentationRegistry.getInstrumentation());
// Set dialer as "android.telephony.cts"
setDefaultDialer(InstrumentationRegistry.getInstrumentation(), PACKAGE_CTS_DIALER);
sSubscriptionManager = InstrumentationRegistry.getInstrumentation()
.getContext().getSystemService(SubscriptionManager.class);
// Get the default Subscription values and save it to restore after test ends.
sPreviousOptInStatus = sSubscriptionManager.getLongSubscriptionProperty(sTestSub,
SubscriptionManager.VOIMS_OPT_IN_STATUS, 0, getContext());
sPreviousEn4GMode = sSubscriptionManager.getLongSubscriptionProperty(sTestSub,
SubscriptionManager.ENHANCED_4G_MODE_ENABLED, 0, getContext());
// Set the new Sunbscription values
sSubscriptionManager.setSubscriptionProperty(sTestSub,
SubscriptionManager.VOIMS_OPT_IN_STATUS, String.valueOf(1));
sSubscriptionManager.setSubscriptionProperty(sTestSub,
SubscriptionManager.ENHANCED_4G_MODE_ENABLED, String.valueOf(1));
//Override the carrier configurartions
CarrierConfigManager configurationManager = InstrumentationRegistry.getInstrumentation()
.getContext().getSystemService(CarrierConfigManager.class);
PersistableBundle bundle = new PersistableBundle(1);
bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_VOLTE_AVAILABLE_BOOL, true);
bundle.putBoolean(CarrierConfigManager.KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL, true);
bundle.putBoolean(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL, false);
bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL, true);
bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_IMS_GBA_REQUIRED_BOOL , false);
sReceiver.clearQueue();
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(configurationManager,
(m) -> m.overrideConfig(sTestSub, bundle));
} finally {
ui.dropShellPermissionIdentity();
}
sReceiver.waitForChanged();
}
public static void afterAllTestsBase() throws Exception {
UiAutomation ui = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
ui.adoptShellPermissionIdentity();
// Set the default Sunbscription values.
sSubscriptionManager.setSubscriptionProperty(sTestSub,
SubscriptionManager.VOIMS_OPT_IN_STATUS, String.valueOf(sPreviousOptInStatus));
sSubscriptionManager.setSubscriptionProperty(sTestSub,
SubscriptionManager.ENHANCED_4G_MODE_ENABLED, String.valueOf(
sPreviousEn4GMode));
// Set default dialer
setDefaultDialer(InstrumentationRegistry.getInstrumentation(), sPreviousDefaultDialer);
// Restore all ImsService configurations that existed before the test.
if (sServiceConnector != null && sIsBound) {
sServiceConnector.disconnectServices();
sIsBound = false;
}
sServiceConnector = null;
overrideCarrierConfig(null);
if (sReceiver != null) {
InstrumentationRegistry.getInstrumentation().getContext()
.unregisterReceiver(sReceiver);
sReceiver = null;
}
} finally {
ui.dropShellPermissionIdentity();
}
}
public void bindImsService() throws Exception {
bindImsService(ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
}
public void bindImsService(int radioTech) throws Exception {
// Connect to the ImsService with the MmTel feature.
assertTrue(sServiceConnector.connectCarrierImsService(new ImsFeatureConfiguration.Builder()
.addFeature(sTestSlot, ImsFeature.FEATURE_MMTEL)
.addFeature(sTestSlot, ImsFeature.FEATURE_EMERGENCY_MMTEL)
.build()));
sIsBound = true;
// The MmTelFeature is created when the ImsService is bound. If it wasn't created, then the
// Framework did not call it.
sServiceConnector.getCarrierService().waitForLatchCountdown(
TestImsService.LATCH_CREATE_MMTEL);
assertNotNull("ImsService created, but ImsService#createMmTelFeature was not called!",
sServiceConnector.getCarrierService().getMmTelFeature());
sServiceConnector.getCarrierService().waitForLatchCountdown(
TestImsService.LATCH_MMTEL_CAP_SET);
MmTelFeature.MmTelCapabilities capabilities = new MmTelFeature.MmTelCapabilities(
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
// Set Registered and VoLTE capable
sServiceConnector.getCarrierService().getImsService().getRegistrationForSubscription(
sTestSlot, sTestSub).onRegistered(radioTech);
sServiceConnector.getCarrierService().getMmTelFeature().setCapabilities(capabilities);
sServiceConnector.getCarrierService().getMmTelFeature()
.notifyCapabilitiesStatusChanged(capabilities);
// Wait a second for the notifyCapabilitiesStatusChanged indication to be processed on the
// main telephony thread - currently no better way of knowing that telephony has processed
// this command. SmsManager#isImsSmsSupported() is @hide and must be updated to use new API.
Thread.sleep(3000);
}
public void waitForUnboundService() {
waitUntilConditionIsTrueOrTimeout(
new Condition() {
@Override
public Object expected() {
return true;
}
@Override
public Object actual() {
InCallServiceStateValidator inCallService = mServiceCallBack.getService();
return (inCallService.isServiceUnBound()) ? true : false;
}
}, WAIT_FOR_SERVICE_TO_UNBOUND, "Service Unbound");
}
public void isCallActive(Call call, TestImsCallSessionImpl callsession) {
assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_ACTIVE, WAIT_FOR_CALL_STATE));
assertNotNull("Unable to get callSession, its null", callsession);
waitUntilConditionIsTrueOrTimeout(
new Condition() {
@Override
public Object expected() {
return true;
}
@Override
public Object actual() {
return (callsession.isInCall()
&& call.getDetails().getState() == Call.STATE_ACTIVE) ? true
: false;
}
}, WAIT_FOR_CONDITION, "Call Active");
}
public void isCallDisconnected(Call call, TestImsCallSessionImpl callsession) {
assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTED, WAIT_FOR_CALL_STATE));
assertNotNull("Unable to get callSession, its null", callsession);
waitUntilConditionIsTrueOrTimeout(
new Condition() {
@Override
public Object expected() {
return true;
}
@Override
public Object actual() {
return (callsession.isInTerminated()
&& call.getDetails().getState() == Call.STATE_DISCONNECTED) ? true
: false;
}
}, WAIT_FOR_CONDITION, "Call Disconnected");
}
protected void setCallID(String callid) {
assertNotNull("Call Id is set to null", callid);
mCurrentCallId = callid;
}
public void addCall(Call call) {
String callid = getCallId(call);
setCallID(callid);
synchronized (mCalls) {
mCalls.put(callid, call);
}
}
public String getCallId(Call call) {
String str = call.toString();
String[] arrofstr = str.split(",", 3);
int index = arrofstr[0].indexOf(":");
String callId = arrofstr[0].substring(index + 1);
return callId;
}
public Call getCall(String callId) {
synchronized (mCalls) {
if (mCalls.isEmpty()) {
return null;
}
for (Map.Entry<String, Call> entry : mCalls.entrySet()) {
if (entry.getKey().equals(callId)) {
Call call = entry.getValue();
assertNotNull("Call is not added, its null", call);
return call;
}
}
}
return null;
}
protected void removeCall(Call call) {
if (mCalls.isEmpty()) {
return;
}
String callid = getCallId(call);
Map.Entry<String, Call>[] entries = mCalls.entrySet().toArray(new Map.Entry[mCalls.size()]);
for (Map.Entry<String, Call> entry : entries) {
if (entry.getKey().equals(callid)) {
mCalls.remove(entry.getKey());
mCurrentCallId = null;
}
}
}
protected class ServiceCallBack extends InCallServiceCallbacks {
@Override
public void onCallAdded(Call call, int numCalls) {
Log.i(LOG_TAG, "onCallAdded, Call: " + call + ", Num Calls: " + numCalls);
addCall(call);
countDownLatch(LATCH_IS_ON_CALL_ADDED);
}
@Override
public void onCallRemoved(Call call, int numCalls) {
Log.i(LOG_TAG, "onCallRemoved, Call: " + call + ", Num Calls: " + numCalls);
removeCall(call);
countDownLatch(LATCH_IS_ON_CALL_REMOVED);
}
@Override
public void onCallStateChanged(Call call, int state) {
Log.i(LOG_TAG, "onCallStateChanged " + state + "Call: " + call);
switch(state) {
case Call.STATE_DIALING : {
countDownLatch(LATCH_IS_CALL_DIALING);
break;
}
case Call.STATE_ACTIVE : {
countDownLatch(LATCH_IS_CALL_ACTIVE);
break;
}
case Call.STATE_DISCONNECTING : {
countDownLatch(LATCH_IS_CALL_DISCONNECTING);
break;
}
case Call.STATE_DISCONNECTED : {
countDownLatch(LATCH_IS_CALL_DISCONNECTED);
break;
}
case Call.STATE_RINGING : {
countDownLatch(LATCH_IS_CALL_RINGING);
break;
}
case Call.STATE_HOLDING : {
countDownLatch(LATCH_IS_CALL_HOLDING);
break;
}
default:
break;
}
}
@Override
public void onChildrenChanged(Call call, List<Call> children) {
if (call.getDetails().hasProperty(Call.Details.PROPERTY_CONFERENCE)) {
Log.i(LOG_TAG, "onChildrenChanged, Call: " + call + " , size " + children.size());
mParticipantCount = children.size();
}
}
@Override
public void onConnectionEvent(Call call, String event, Bundle extras) {
Log.i(LOG_TAG, "onConnectionEvent, Call: " + call + " , event " + event);
if (event.equals(android.telecom.Connection.EVENT_CALL_REMOTELY_HELD)) {
countDownLatch(LATCH_IS_ON_CALL_REMOTELY_HELD);
} else if (event.equals(android.telecom.Connection.EVENT_CALL_REMOTELY_UNHELD)) {
countDownLatch(LATCH_IS_ON_CALL_REMOTELY_UNHELD);
}
}
}
protected static Context getContext() {
return InstrumentationRegistry.getInstrumentation().getContext();
}
/** Checks whether the system feature is supported. */
protected static boolean hasFeature(String feature) {
final PackageManager pm = getContext().getPackageManager();
if (!pm.hasSystemFeature(feature)) {
Log.d(LOG_TAG, "Skipping test that requires " + feature);
return false;
}
return true;
}
protected static String setDefaultDialer(Instrumentation instrumentation, String packageName)
throws Exception {
String str = TelephonyUtils.executeShellCommand(instrumentation, COMMAND_SET_DEFAULT_DIALER
+ packageName);
return str;
}
protected static String getDefaultDialer(Instrumentation instrumentation) throws Exception {
String str = TelephonyUtils.executeShellCommand(instrumentation,
COMMAND_GET_DEFAULT_DIALER);
return str;
}
protected static void overrideCarrierConfig(PersistableBundle bundle) throws Exception {
CarrierConfigManager carrierConfigManager = InstrumentationRegistry.getInstrumentation()
.getContext().getSystemService(CarrierConfigManager.class);
sReceiver.clearQueue();
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(carrierConfigManager,
(m) -> m.overrideConfig(sTestSub, bundle));
sReceiver.waitForChanged();
}
}