Merge changes from topic "u-domain-selection-emergency-call"
* changes:
Add EmergencyCallDomainSelector
Add DomainSelectionService for domain selection
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 0e32e40..32733cc 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -516,7 +516,6 @@
<activity android:name="com.android.phone.settings.VoicemailSettingsActivity"
android:label="@string/voicemail"
android:configChanges="orientation|screenSize|keyboardHidden|screenLayout"
- android:screenOrientation="portrait"
android:exported="true"
android:theme="@style/CallSettingsWithoutDividerTheme">
<intent-filter >
diff --git a/src/com/android/phone/CarrierConfigLoader.java b/src/com/android/phone/CarrierConfigLoader.java
index 44375bb..397ed35 100644
--- a/src/com/android/phone/CarrierConfigLoader.java
+++ b/src/com/android/phone/CarrierConfigLoader.java
@@ -80,6 +80,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
/**
@@ -194,6 +195,14 @@
// requested the dump.
private static final String DUMP_ARG_REQUESTING_PACKAGE = "--requesting-package";
+ // Configs that should always be included when clients calls getConfig[ForSubId] with specified
+ // keys (even configs are not explicitly specified). Those configs have special purpose for the
+ // carrier config APIs to work correctly.
+ private static final String[] CONFIG_SUBSET_METADATA_KEYS = new String[] {
+ CarrierConfigManager.KEY_CARRIER_CONFIG_VERSION_STRING,
+ CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL
+ };
+
// Handler to process various events.
//
// For each phoneId, the event sequence should be:
@@ -1329,6 +1338,52 @@
}
@Override
+ @NonNull
+ public PersistableBundle getConfigSubsetForSubIdWithFeature(int subscriptionId,
+ @NonNull String callingPackage, @Nullable String callingFeatureId,
+ @NonNull String[] keys) {
+ Objects.requireNonNull(callingPackage, "Calling package must be non-null");
+ Objects.requireNonNull(keys, "Config keys must be non-null");
+ enforceCallerIsSystemOrRequestingPackage(callingPackage);
+
+ // Permission check is performed inside and an empty bundle will return on failure.
+ // No SecurityException thrown here since most clients expect to retrieve the overridden
+ // value if present or use default one if not
+ PersistableBundle allConfigs = getConfigForSubIdWithFeature(subscriptionId, callingPackage,
+ callingFeatureId);
+ if (allConfigs.isEmpty()) {
+ return allConfigs;
+ }
+ for (String key : keys) {
+ Objects.requireNonNull(key, "Config key must be non-null");
+ // TODO(b/261776046): validate provided key which may has no default value.
+ // For now, return empty bundle if any required key is not supported
+ if (!allConfigs.containsKey(key)) {
+ return new PersistableBundle();
+ }
+ }
+
+ PersistableBundle configSubset = new PersistableBundle(
+ keys.length + CONFIG_SUBSET_METADATA_KEYS.length);
+ for (String carrierConfigKey : keys) {
+ Object value = allConfigs.get(carrierConfigKey);
+ // Config value itself could be PersistableBundle which requires different API to put
+ if (value instanceof PersistableBundle) {
+ configSubset.putPersistableBundle(carrierConfigKey, (PersistableBundle) value);
+ } else {
+ configSubset.putObject(carrierConfigKey, value);
+ }
+ }
+
+ // Configs in CONFIG_SUBSET_ALWAYS_INCLUDED_KEYS should always be included
+ for (String generalKey : CONFIG_SUBSET_METADATA_KEYS) {
+ configSubset.putObject(generalKey, allConfigs.get(generalKey));
+ }
+
+ return configSubset;
+ }
+
+ @Override
public void overrideConfig(int subscriptionId, @Nullable PersistableBundle overrides,
boolean persistent) {
mContext.enforceCallingOrSelfPermission(
diff --git a/src/com/android/services/telephony/PstnIncomingCallNotifier.java b/src/com/android/services/telephony/PstnIncomingCallNotifier.java
index f78a9b9..d58c211 100644
--- a/src/com/android/services/telephony/PstnIncomingCallNotifier.java
+++ b/src/com/android/services/telephony/PstnIncomingCallNotifier.java
@@ -393,8 +393,9 @@
*/
private PhoneAccountHandle findCorrectPhoneAccountHandle() {
TelecomAccountRegistry telecomAccountRegistry = TelecomAccountRegistry.getInstance(null);
- // Check to see if a the SIM PhoneAccountHandle Exists for the Call.
- PhoneAccountHandle handle = PhoneUtils.makePstnPhoneAccountHandle(mPhone);
+ // Check to see if a SIM PhoneAccountHandle Exists for the Call.
+ PhoneAccountHandle handle = telecomAccountRegistry.getPhoneAccountHandleForSubId(
+ mPhone.getSubId());
if (telecomAccountRegistry.hasAccountEntryForPhoneAccount(handle)) {
return handle;
}
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 962053c..9d36c48 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -48,11 +48,13 @@
import android.telephony.ServiceState.RilRadioTechnology;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsCallProfile;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.ImsStreamMediaProfile;
import android.telephony.ims.RtpHeaderExtension;
import android.telephony.ims.RtpHeaderExtensionType;
+import android.telephony.ims.feature.MmTelFeature;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Pair;
@@ -801,6 +803,13 @@
Log.i(this, "onReceivedDtmfDigit: digit=%c", digit);
mDtmfTransport.onDtmfReceived(digit);
}
+
+ @Override
+ public void onAudioModeIsVoipChanged(int imsAudioHandler) {
+ boolean isVoip = imsAudioHandler == MmTelFeature.AUDIO_HANDLER_ANDROID;
+ Log.i(this, "onAudioModeIsVoipChanged isVoip =" + isVoip);
+ setAudioModeIsVoip(isVoip);
+ }
};
private TelephonyConnectionService mTelephonyConnectionService;
@@ -925,6 +934,8 @@
private final Set<TelephonyConnectionListener> mTelephonyListeners = Collections.newSetFromMap(
new ConcurrentHashMap<TelephonyConnectionListener, Boolean>(8, 0.9f, 1));
+ private Integer mEmergencyServiceCategory = null;
+
protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection,
String callId, @android.telecom.Call.Details.CallDirection int callDirection) {
setCallDirection(callDirection);
@@ -2158,7 +2169,7 @@
mPhoneForEvents = null;
}
- @VisibleForTesting
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
public void hangup(int telephonyDisconnectCode) {
if (mOriginalConnection != null) {
mHangupDisconnectCause = telephonyDisconnectCode;
@@ -2184,6 +2195,7 @@
Log.e(this, e, "Call to Connection.hangup failed with exception");
}
} else {
+ mTelephonyConnectionService.onLocalHangup(this);
if (getState() == STATE_DISCONNECTED) {
Log.i(this, "hangup called on an already disconnected call!");
close();
@@ -2444,6 +2456,29 @@
setTelephonyConnectionRinging();
break;
case DISCONNECTED:
+ if (mTelephonyConnectionService != null) {
+ ImsReasonInfo reasonInfo = null;
+ if (isImsConnection()) {
+ ImsPhoneConnection imsPhoneConnection =
+ (ImsPhoneConnection) mOriginalConnection;
+ reasonInfo = imsPhoneConnection.getImsReasonInfo();
+ if (reasonInfo != null && reasonInfo.getCode()
+ == ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL) {
+ EmergencyNumber emergencyNumber =
+ imsPhoneConnection.getEmergencyNumberInfo();
+ if (emergencyNumber != null) {
+ mEmergencyServiceCategory =
+ emergencyNumber.getEmergencyServiceCategoryBitmask();
+ }
+ }
+ }
+
+ if (mTelephonyConnectionService.maybeReselectDomain(this,
+ mOriginalConnection.getPreciseDisconnectCause(), reasonInfo)) {
+ break;
+ }
+ }
+
if (shouldTreatAsEmergencyCall()
&& (cause
== android.telephony.DisconnectCause.EMERGENCY_TEMP_FAILURE
@@ -3831,4 +3866,21 @@
public List<TelephonyConnectionListener> getTelephonyConnectionListeners() {
return new ArrayList<>(mTelephonyListeners);
}
+
+ /**
+ * @return An {@link Integer} instance of the emergency service category.
+ */
+ public @Nullable Integer getEmergencyServiceCategory() {
+ return mEmergencyServiceCategory;
+ }
+
+ /**
+ * Sets the emergency service category.
+ *
+ * @param eccCategory The emergency service category.
+ */
+ @VisibleForTesting
+ public void setEmergencyServiceCategory(int eccCategory) {
+ mEmergencyServiceCategory = eccCategory;
+ }
}
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index e86a1ae..172407a 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -16,6 +16,7 @@
package com.android.services.telephony;
+import static android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING;
import static android.telephony.TelephonyManager.HAL_SERVICE_VOICE;
import android.annotation.NonNull;
@@ -40,17 +41,23 @@
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
+import android.telephony.Annotation.DisconnectCauses;
import android.telephony.CarrierConfigManager;
+import android.telephony.DomainSelectionService;
+import android.telephony.EmergencyRegResult;
+import android.telephony.NetworkRegistrationInfo;
import android.telephony.PhoneNumberUtils;
import android.telephony.RadioAccessFamily;
import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.emergency.EmergencyNumber;
+import android.telephony.ims.ImsReasonInfo;
import android.text.TextUtils;
import android.util.Pair;
import android.view.WindowManager;
+import com.android.ims.ImsManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
@@ -64,6 +71,10 @@
import com.android.internal.telephony.SubscriptionController;
import com.android.internal.telephony.d2d.Communicator;
import com.android.internal.telephony.data.PhoneSwitcher;
+import com.android.internal.telephony.domainselection.DomainSelectionConnection;
+import com.android.internal.telephony.domainselection.DomainSelectionResolver;
+import com.android.internal.telephony.domainselection.EmergencyCallDomainSelectionConnection;
+import com.android.internal.telephony.emergency.EmergencyStateTracker;
import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.imsphone.ImsPhoneConnection;
@@ -89,6 +100,7 @@
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.regex.Pattern;
@@ -179,6 +191,13 @@
@VisibleForTesting
public Pair<WeakReference<TelephonyConnection>, Queue<Phone>> mEmergencyRetryCache;
private DeviceState mDeviceState = new DeviceState();
+ private EmergencyStateTracker mEmergencyStateTracker;
+ private DomainSelectionResolver mDomainSelectionResolver;
+ private EmergencyCallDomainSelectionConnection mEmergencyCallDomainSelectionConnection;
+ private TelephonyConnection mEmergencyConnection;
+ private String mEmergencyCallId = null;
+ private Executor mDomainSelectionMainExecutor;
+ private ImsManager mImsManager = null;
/**
* Keeps track of the status of a SIM slot.
@@ -504,6 +523,42 @@
}
/**
+ * A listener for emergency calls.
+ */
+ private final TelephonyConnection.TelephonyConnectionListener mEmergencyConnectionListener =
+ new TelephonyConnection.TelephonyConnectionListener() {
+ @Override
+ public void onStateChanged(Connection connection, @Connection.ConnectionState int state) {
+ if (connection != null) {
+ TelephonyConnection c = (TelephonyConnection) connection;
+ Log.i(this, "onStateChanged callId=" + c.getTelecomCallId() + ", state=" + state);
+ if (c.getState() == Connection.STATE_ACTIVE) {
+ mEmergencyStateTracker.onEmergencyCallStateChanged(
+ c.getOriginalConnection().getState(), c.getTelecomCallId());
+ c.removeTelephonyConnectionListener(mEmergencyConnectionListener);
+ releaseEmergencyCallDomainSelection(false);
+ }
+ }
+ }
+ };
+
+ private final DomainSelectionConnection.DomainSelectionConnectionCallback
+ mEmergencyDomainSelectionConnectionCallback =
+ new DomainSelectionConnection.DomainSelectionConnectionCallback() {
+ @Override
+ public void onSelectionTerminated(@DisconnectCauses int cause) {
+ if (mEmergencyCallDomainSelectionConnection != null) {
+ Log.i(this, "onSelectionTerminated cause=" + cause);
+ mEmergencyCallDomainSelectionConnection = null;
+ if (mEmergencyConnection != null) {
+ mEmergencyConnection.hangup(android.telephony.DisconnectCause.OUT_OF_NETWORK);
+ mEmergencyConnection = null;
+ }
+ }
+ }
+ };
+
+ /**
* A listener to actionable events specific to the TelephonyConnection.
*/
private final TelephonyConnection.TelephonyConnectionListener mTelephonyConnectionListener =
@@ -542,6 +597,8 @@
TelecomAccountRegistry.getInstance(this).setTelephonyConnectionService(this);
mHoldTracker = new HoldTracker();
mIsTtyEnabled = mDeviceState.isTtyModeEnabled(this);
+ mDomainSelectionMainExecutor = getApplicationContext().getMainExecutor();
+ mDomainSelectionResolver = DomainSelectionResolver.getInstance();
IntentFilter intentFilter = new IntentFilter(
TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED);
@@ -809,6 +866,16 @@
/* Note: when not an emergency, handle can be null for unknown callers */
handle == null ? null : handle.getSchemeSpecificPart());
+ if (mDomainSelectionResolver.isDomainSelectionSupported()) {
+ if (isEmergencyNumber) {
+ final Connection resultConnection =
+ placeEmergencyConnection(phone,
+ request, numberToDial, isTestEmergencyNumber,
+ handle, needToTurnOnRadio);
+ if (resultConnection != null) return resultConnection;
+ }
+ }
+
if (needToTurnOnRadio) {
final Uri resultHandle = handle;
final int originalPhoneType = phone.getPhoneType();
@@ -1715,6 +1782,10 @@
}
updatePhoneAccount(c, newPhoneToUse);
}
+ if (mDomainSelectionResolver.isDomainSelectionSupported()) {
+ onEmergencyRedial(c, newPhoneToUse);
+ return;
+ }
placeOutgoingConnection(c, newPhoneToUse, videoState, connExtras);
} else {
// We have run out of Phones to use. Disconnect the call and destroy the connection.
@@ -1770,6 +1841,7 @@
try {
if (phone != null) {
boolean isEmergency = mTelephonyManagerProxy.isCurrentEmergencyNumber(number);
+ Log.i(this, "placeOutgoingConnection isEmergency=" + isEmergency);
if (isEmergency) {
if (!getAllConnections().isEmpty()) {
if (!shouldHoldForEmergencyCall(phone)) {
@@ -1862,6 +1934,366 @@
}
}
+ @SuppressWarnings("FutureReturnValueIgnored")
+ private Connection placeEmergencyConnection(
+ final Phone phone, final ConnectionRequest request,
+ final String numberToDial, final boolean isTestEmergencyNumber,
+ final Uri handle, final boolean needToTurnOnRadio) {
+
+ final Connection resultConnection =
+ getTelephonyConnection(request, numberToDial, true, handle, phone);
+
+ if (resultConnection instanceof TelephonyConnection) {
+ Log.i(this, "placeEmergencyConnection");
+
+ mIsEmergencyCallPending = true;
+ ((TelephonyConnection) resultConnection).addTelephonyConnectionListener(
+ mEmergencyConnectionListener);
+
+ if (mEmergencyStateTracker == null) {
+ mEmergencyStateTracker = EmergencyStateTracker.getInstance();
+ }
+
+ mEmergencyCallId = resultConnection.getTelecomCallId();
+ CompletableFuture<Integer> future = mEmergencyStateTracker.startEmergencyCall(
+ phone, mEmergencyCallId, isTestEmergencyNumber);
+ future.thenAccept((result) -> {
+ Log.d(this, "startEmergencyCall-complete result=" + result);
+ if (result == android.telephony.DisconnectCause.NOT_DISCONNECTED) {
+ createEmergencyConnection(phone, (TelephonyConnection) resultConnection,
+ numberToDial, request, needToTurnOnRadio,
+ mEmergencyStateTracker.getEmergencyRegResult());
+ } else {
+ mEmergencyConnection = null;
+ String reason = "Couldn't setup emergency call";
+ if (result == android.telephony.DisconnectCause.POWER_OFF) {
+ reason = "Failed to turn on radio.";
+ }
+ ((TelephonyConnection) resultConnection).setTelephonyConnectionDisconnected(
+ mDisconnectCauseFactory.toTelecomDisconnectCause(result, reason));
+ ((TelephonyConnection) resultConnection).close();
+ mIsEmergencyCallPending = false;
+ }
+ });
+ mEmergencyConnection = (TelephonyConnection) resultConnection;
+ return resultConnection;
+ }
+ Log.i(this, "placeEmergencyConnection returns null");
+ return null;
+ }
+
+ @SuppressWarnings("FutureReturnValueIgnored")
+ private void createEmergencyConnection(final Phone phone,
+ final TelephonyConnection resultConnection, final String number,
+ final ConnectionRequest request, boolean needToTurnOnRadio,
+ final EmergencyRegResult regResult) {
+ Log.i(this, "createEmergencyConnection");
+
+ if (phone.getImsPhone() == null) {
+ // Dialing emergency calls over IMS is not available without ImsPhone instance.
+ Log.w(this, "createEmergencyConnection no ImsPhone");
+ dialCsEmergencyCall(phone, resultConnection, request);
+ return;
+ }
+
+ ImsManager imsManager = mImsManager;
+ if (imsManager == null) {
+ // mImsManager is not null only while unit test.
+ imsManager = ImsManager.getInstance(phone.getContext(), phone.getPhoneId());
+ }
+ if (!imsManager.isNonTtyOrTtyOnVolteEnabled()) {
+ Log.w(this, "createEmergencyConnection - TTY on VoLTE is not supported.");
+ dialCsEmergencyCall(phone, resultConnection, request);
+ return;
+ }
+
+ DomainSelectionConnection selectConnection =
+ mDomainSelectionResolver.getDomainSelectionConnection(
+ phone, SELECTOR_TYPE_CALLING, true);
+
+ if (selectConnection == null) {
+ // While the domain selection service is enabled, the valid
+ // {@link DomainSelectionConnection} is not available.
+ // This can happen when the domain selection service is not available.
+ Log.w(this, "createEmergencyConnection - no selectionConnection");
+ dialCsEmergencyCall(phone, resultConnection, request);
+ return;
+ }
+
+ mEmergencyCallDomainSelectionConnection =
+ (EmergencyCallDomainSelectionConnection) selectConnection;
+
+ DomainSelectionService.SelectionAttributes attr =
+ EmergencyCallDomainSelectionConnection.getSelectionAttributes(
+ phone.getPhoneId(), phone.getSubId(), needToTurnOnRadio,
+ request.getTelecomCallId(), number, 0, null, regResult);
+
+ CompletableFuture<Integer> future =
+ mEmergencyCallDomainSelectionConnection.createEmergencyConnection(
+ attr, mEmergencyDomainSelectionConnectionCallback);
+ future.thenAcceptAsync((result) -> {
+ Log.d(this, "createEmergencyConnection-complete result=" + result);
+ Bundle extras = request.getExtras();
+ extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, result);
+ placeOutgoingConnection(request, resultConnection, phone);
+ mIsEmergencyCallPending = false;
+ }, mDomainSelectionMainExecutor);
+ }
+
+ private void dialCsEmergencyCall(final Phone phone,
+ final TelephonyConnection resultConnection, final ConnectionRequest request) {
+ Log.d(this, "dialCsEmergencyCall");
+ Bundle extras = request.getExtras();
+ extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, NetworkRegistrationInfo.DOMAIN_CS);
+ mDomainSelectionMainExecutor.execute(
+ () -> placeOutgoingConnection(request, resultConnection, phone));
+ }
+
+ private void releaseEmergencyCallDomainSelection(boolean cancel) {
+ if (mEmergencyCallDomainSelectionConnection != null) {
+ if (cancel) mEmergencyCallDomainSelectionConnection.cancelSelection();
+ else mEmergencyCallDomainSelectionConnection.finishSelection();
+ mEmergencyCallDomainSelectionConnection = null;
+ }
+ mIsEmergencyCallPending = false;
+ mEmergencyConnection = null;
+ }
+
+ /**
+ * Determine whether reselection of domain is required or not.
+ * @param c the {@link Connection} instance.
+ * @param callFailCause the reason why CS call is disconnected. Allowed values are defined in
+ * {@link com.android.internal.telephony.CallFailCause}.
+ * @param reasonInfo the reason why PS call is disconnected.
+ * @return {@code true} if reselection of domain is required.
+ */
+ public boolean maybeReselectDomain(final TelephonyConnection c,
+ int callFailCause, ImsReasonInfo reasonInfo) {
+ if (!mDomainSelectionResolver.isDomainSelectionSupported()) return false;
+
+ Log.i(this, "maybeReselectDomain csCause=" + callFailCause + ", psCause=" + reasonInfo);
+ if (TextUtils.equals(mEmergencyCallId, c.getTelecomCallId())) {
+ if (mEmergencyCallDomainSelectionConnection != null) {
+ return maybeReselectDomainForEmergencyCall(c, callFailCause, reasonInfo);
+ }
+ Log.i(this, "maybeReselectDomain endCall()");
+ mEmergencyStateTracker.endCall(c.getTelecomCallId());
+ mEmergencyCallId = null;
+ return false;
+ }
+
+ if (reasonInfo != null
+ && reasonInfo.getCode() == ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL) {
+ onEmergencyRedial(c, c.getPhone());
+ return true;
+ }
+
+ return false;
+ }
+
+ private boolean maybeReselectDomainForEmergencyCall(final TelephonyConnection c,
+ int callFailCause, ImsReasonInfo reasonInfo) {
+ Log.i(this, "maybeReselectDomainForEmergencyCall "
+ + "csCause=" + callFailCause + ", psCause=" + reasonInfo);
+
+ // EMERGENCY_TEMP_FAILURE and EMERGENCY_PERM_FAILURE shall be handled after
+ // reselecting new {@link Phone} in {@link #retryOutgoingOriginalConnection()}.
+ if (c.getOriginalConnection() != null
+ && c.getOriginalConnection().getDisconnectCause()
+ != android.telephony.DisconnectCause.EMERGENCY_TEMP_FAILURE
+ && c.getOriginalConnection().getDisconnectCause()
+ != android.telephony.DisconnectCause.EMERGENCY_PERM_FAILURE
+ && c.getOriginalConnection().getDisconnectCause()
+ != android.telephony.DisconnectCause.LOCAL
+ && c.getOriginalConnection().getDisconnectCause()
+ != android.telephony.DisconnectCause.POWER_OFF) {
+
+ DomainSelectionService.SelectionAttributes attr =
+ EmergencyCallDomainSelectionConnection.getSelectionAttributes(
+ c.getPhone().getPhoneId(), c.getPhone().getSubId(), false,
+ c.getTelecomCallId(), c.getAddress().getSchemeSpecificPart(),
+ callFailCause, reasonInfo, null);
+
+ CompletableFuture<Integer> future =
+ mEmergencyCallDomainSelectionConnection.reselectDomain(attr);
+ if (future != null) {
+ future.thenAcceptAsync((result) -> {
+ Log.d(this, "reselectDomain-complete");
+ onEmergencyRedialOnDomain(c, c.getPhone(), result);
+ }, mDomainSelectionMainExecutor);
+ return true;
+ }
+ }
+
+ Log.i(this, "maybeReselectDomainForEmergencyCall endCall()");
+ c.removeTelephonyConnectionListener(mEmergencyConnectionListener);
+ mEmergencyStateTracker.endCall(c.getTelecomCallId());
+ releaseEmergencyCallDomainSelection(true);
+
+ return false;
+ }
+
+ private void onEmergencyRedialOnDomain(TelephonyConnection connection,
+ final Phone phone, @NetworkRegistrationInfo.Domain int domain) {
+ Log.i(this, "onEmergencyRedialOnDomain phoneId=" + phone.getPhoneId()
+ + ", domain=" + DomainSelectionService.getDomainName(domain));
+
+ String number = connection.getAddress().getSchemeSpecificPart();
+
+ // Indicates undetectable emergency number with DialArgs
+ boolean isEmergency = false;
+ int eccCategory = EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED;
+ if (connection.getEmergencyServiceCategory() != null) {
+ isEmergency = true;
+ eccCategory = connection.getEmergencyServiceCategory();
+ Log.i(this, "onEmergencyRedialOnDomain eccCategory=" + eccCategory);
+ }
+
+ Bundle extras = new Bundle();
+ extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, domain);
+
+ com.android.internal.telephony.Connection originalConnection =
+ connection.getOriginalConnection();
+ try {
+ if (phone != null) {
+ originalConnection = phone.dial(number, new ImsPhone.ImsDialArgs.Builder()
+ .setVideoState(VideoProfile.STATE_AUDIO_ONLY)
+ .setIntentExtras(extras)
+ .setRttTextStream(connection.getRttTextStream())
+ .setIsEmergency(isEmergency)
+ .setEccCategory(eccCategory)
+ .build(),
+ connection::registerForCallEvents);
+ }
+ } catch (CallStateException e) {
+ Log.e(this, e, "onEmergencyRedialOnDomain, exception: " + e);
+ }
+ if (originalConnection == null) {
+ Log.d(this, "onEmergencyRedialOnDomain, phone.dial returned null");
+ connection.setDisconnected(
+ mDisconnectCauseFactory.toTelecomDisconnectCause(
+ android.telephony.DisconnectCause.ERROR_UNSPECIFIED,
+ "unknown error"));
+ } else {
+ connection.setOriginalConnection(originalConnection);
+ }
+ }
+
+ @SuppressWarnings("FutureReturnValueIgnored")
+ private void onEmergencyRedial(final TelephonyConnection c, final Phone phone) {
+ Log.i(this, "onEmergencyRedial phoneId=" + phone.getPhoneId());
+
+ final String number = c.getAddress().getSchemeSpecificPart();
+ final boolean isTestEmergencyNumber = isEmergencyNumberTestNumber(number);
+
+ mIsEmergencyCallPending = true;
+ c.addTelephonyConnectionListener(mEmergencyConnectionListener);
+
+ if (mEmergencyStateTracker == null) {
+ mEmergencyStateTracker = EmergencyStateTracker.getInstance();
+ }
+
+ mEmergencyCallId = c.getTelecomCallId();
+ CompletableFuture<Integer> future = mEmergencyStateTracker.startEmergencyCall(
+ phone, mEmergencyCallId, isTestEmergencyNumber);
+ future.thenAccept((result) -> {
+ Log.d(this, "onEmergencyRedial-complete result=" + result);
+ if (result == android.telephony.DisconnectCause.NOT_DISCONNECTED) {
+
+ DomainSelectionConnection selectConnection =
+ mDomainSelectionResolver.getDomainSelectionConnection(
+ phone, SELECTOR_TYPE_CALLING, true);
+
+ if (selectConnection == null) {
+ Log.w(this, "onEmergencyRedial no selectionConnection, dial CS emergency call");
+ mIsEmergencyCallPending = false;
+ mDomainSelectionMainExecutor.execute(
+ () -> recreateEmergencyConnection(c, phone,
+ NetworkRegistrationInfo.DOMAIN_CS));
+ return;
+ }
+
+ mEmergencyCallDomainSelectionConnection =
+ (EmergencyCallDomainSelectionConnection) selectConnection;
+
+ mEmergencyConnection = c;
+
+ DomainSelectionService.SelectionAttributes attr =
+ EmergencyCallDomainSelectionConnection.getSelectionAttributes(
+ phone.getPhoneId(),
+ phone.getSubId(), false,
+ c.getTelecomCallId(),
+ c.getAddress().getSchemeSpecificPart(),
+ 0, null, mEmergencyStateTracker.getEmergencyRegResult());
+
+ CompletableFuture<Integer> domainFuture =
+ mEmergencyCallDomainSelectionConnection.createEmergencyConnection(
+ attr, mEmergencyDomainSelectionConnectionCallback);
+ domainFuture.thenAcceptAsync((domain) -> {
+ Log.d(this, "onEmergencyRedial-createEmergencyConnection-complete domain="
+ + domain);
+ recreateEmergencyConnection(c, phone, domain);
+ mIsEmergencyCallPending = false;
+ }, mDomainSelectionMainExecutor);
+ } else {
+ c.setTelephonyConnectionDisconnected(
+ mDisconnectCauseFactory.toTelecomDisconnectCause(result, "unknown error"));
+ c.close();
+ mIsEmergencyCallPending = false;
+ }
+ });
+ }
+
+ private void recreateEmergencyConnection(final TelephonyConnection connection,
+ final Phone phone, final @NetworkRegistrationInfo.Domain int result) {
+ Log.d(this, "recreateEmergencyConnection result=" + result);
+ if (!getAllConnections().isEmpty()) {
+ if (!shouldHoldForEmergencyCall(phone)) {
+ // If we do not support holding ongoing calls for an outgoing
+ // emergency call, disconnect the ongoing calls.
+ for (Connection c : getAllConnections()) {
+ if (!c.equals(connection)
+ && c.getState() != Connection.STATE_DISCONNECTED
+ && c instanceof TelephonyConnection) {
+ ((TelephonyConnection) c).hangup(
+ android.telephony.DisconnectCause
+ .OUTGOING_EMERGENCY_CALL_PLACED);
+ }
+ }
+ for (Conference c : getAllConferences()) {
+ if (c.getState() != Connection.STATE_DISCONNECTED) {
+ c.onDisconnect();
+ }
+ }
+ } else if (!isVideoCallHoldAllowed(phone)) {
+ // If we do not support holding ongoing video call for an outgoing
+ // emergency call, disconnect the ongoing video call.
+ for (Connection c : getAllConnections()) {
+ if (!c.equals(connection)
+ && c.getState() == Connection.STATE_ACTIVE
+ && VideoProfile.isVideo(c.getVideoState())
+ && c instanceof TelephonyConnection) {
+ ((TelephonyConnection) c).hangup(
+ android.telephony.DisconnectCause
+ .OUTGOING_EMERGENCY_CALL_PLACED);
+ break;
+ }
+ }
+ }
+ }
+ onEmergencyRedialOnDomain(connection, phone, result);
+ }
+
+ protected void onLocalHangup(TelephonyConnection c) {
+ if (TextUtils.equals(mEmergencyCallId, c.getTelecomCallId())) {
+ Log.i(this, "onLocalHangup " + mEmergencyCallId);
+ c.removeTelephonyConnectionListener(mEmergencyConnectionListener);
+ mEmergencyStateTracker.endCall(c.getTelecomCallId());
+ releaseEmergencyCallDomainSelection(true);
+ mEmergencyCallId = null;
+ }
+ }
+
private boolean isVideoCallHoldAllowed(Phone phone) {
CarrierConfigManager cfgManager = (CarrierConfigManager)
phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
diff --git a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
index 85be48d..1208ee2 100644
--- a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
@@ -16,6 +16,13 @@
package com.android.services.telephony;
+import static android.telephony.DisconnectCause.NOT_DISCONNECTED;
+import static android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_CS;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+import static android.telephony.emergency.EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE;
+import static android.telephony.ims.ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL;
+
import static com.android.internal.telephony.RILConstants.GSM_PHONE;
import static junit.framework.Assert.assertEquals;
@@ -25,14 +32,15 @@
import static junit.framework.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -46,26 +54,33 @@
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
-import android.telephony.CarrierConfigManager;
import android.telephony.RadioAccessFamily;
import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.emergency.EmergencyNumber;
+import android.telephony.ims.ImsReasonInfo;
import android.test.suitebuilder.annotation.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.TelephonyTestBase;
+import com.android.ims.ImsManager;
import com.android.internal.telecom.IConnectionService;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.Connection;
import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneInternalInterface.DialArgs;
import com.android.internal.telephony.ServiceStateTracker;
import com.android.internal.telephony.data.PhoneSwitcher;
+import com.android.internal.telephony.domainselection.DomainSelectionResolver;
+import com.android.internal.telephony.domainselection.EmergencyCallDomainSelectionConnection;
import com.android.internal.telephony.emergency.EmergencyNumberTracker;
+import com.android.internal.telephony.emergency.EmergencyStateTracker;
import com.android.internal.telephony.gsm.SuppServiceNotification;
+import com.android.internal.telephony.imsphone.ImsPhone;
import org.junit.After;
import org.junit.Before;
@@ -73,11 +88,14 @@
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
+import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
/**
* Unit tests for TelephonyConnectionService.
@@ -116,6 +134,8 @@
private static final PhoneAccountHandle PHONE_ACCOUNT_HANDLE_2 = new PhoneAccountHandle(
TEST_COMPONENT_NAME, TEST_ACCOUNT_ID2);
private static final Uri TEST_ADDRESS = Uri.parse("tel:+16505551212");
+ private static final String TELECOM_CALL_ID1 = "TC1";
+ private static final String TEST_EMERGENCY_NUMBER = "911";
private android.telecom.Connection mConnection;
@Mock TelephonyConnectionService.TelephonyManagerProxy mTelephonyManagerProxy;
@@ -135,6 +155,10 @@
@Mock Call mCall2;
@Mock com.android.internal.telephony.Connection mInternalConnection;
@Mock com.android.internal.telephony.Connection mInternalConnection2;
+ @Mock DomainSelectionResolver mDomainSelectionResolver;
+ @Mock EmergencyCallDomainSelectionConnection mEmergencyCallDomainSelectionConnection;
+ @Mock ImsPhone mImsPhone;
+ private EmergencyStateTracker mEmergencyStateTracker;
private Phone mPhone0;
private Phone mPhone1;
@@ -180,6 +204,20 @@
mTestConnectionService.setDisconnectCauseFactory(mDisconnectCauseFactory);
mTestConnectionService.onCreate();
mTestConnectionService.setTelephonyManagerProxy(mTelephonyManagerProxy);
+ DomainSelectionResolver.setDomainSelectionResolver(mDomainSelectionResolver);
+ replaceInstance(TelephonyConnectionService.class, "mDomainSelectionResolver",
+ mTestConnectionService, mDomainSelectionResolver);
+ mEmergencyStateTracker = Mockito.mock(EmergencyStateTracker.class);
+ replaceInstance(TelephonyConnectionService.class, "mEmergencyStateTracker",
+ mTestConnectionService, mEmergencyStateTracker);
+ doReturn(CompletableFuture.completedFuture(NOT_DISCONNECTED))
+ .when(mEmergencyStateTracker)
+ .startEmergencyCall(any(), anyString(), eq(false));
+ replaceInstance(TelephonyConnectionService.class,
+ "mDomainSelectionMainExecutor", mTestConnectionService, getExecutor());
+ doReturn(false).when(mDomainSelectionResolver).isDomainSelectionSupported();
+ doReturn(null).when(mDomainSelectionResolver).getDomainSelectionConnection(
+ any(), anyInt(), anyBoolean());
mBinderStub = (IConnectionService.Stub) mTestConnectionService.onBind(null);
}
@@ -1107,221 +1145,6 @@
}
/**
- * Test that the TelephonyConnectionService successfully performs a DDS switch before a call
- * when we are not roaming and the carrier only supports SUPL over the data plane.
- */
- @Test
- @SmallTest
- public void testCreateOutgoingEmergencyConnection_delayDial_carrierconfig_dds() {
- // Setup test to not support SUPL on the non-DDS subscription
- doReturn(true).when(mDeviceState).isSuplDdsSwitchRequiredForEmergencyCall(any());
- getTestContext().getCarrierConfig(0 /*subId*/).putStringArray(
- CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY,
- null);
- getTestContext().getCarrierConfig(0 /*subId*/).putInt(
- CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
- CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY);
- getTestContext().getCarrierConfig(0 /*subId*/).putString(
- CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "150");
-
- Phone testPhone = setupConnectionServiceForDelayDial(
- false /* isRoaming */, false /* setOperatorName */, null /* operator long name*/,
- null /* operator short name */, null /* operator numeric name */);
- verify(mPhoneSwitcher).overrideDefaultDataForEmergency(eq(0) /*phoneId*/ ,
- eq(150) /*extensionTime*/, any());
- }
-
- /**
- * Test that the TelephonyConnectionService successfully turns radio on before placing the
- * emergency call.
- */
- @Test
- @SmallTest
- public void testCreateOutgoingEmerge_exitingApm_disconnected() {
- when(mDeviceState.isAirplaneModeOn(any())).thenReturn(true);
- Phone testPhone = setupConnectionServiceInApm();
-
- ArgumentCaptor<RadioOnStateListener.Callback> callback =
- ArgumentCaptor.forClass(RadioOnStateListener.Callback.class);
- verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true),
- eq(testPhone), eq(false));
-
- assertFalse(callback.getValue().isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE));
- when(mSST.isRadioOn()).thenReturn(true);
- assertTrue(callback.getValue().isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE));
-
- mConnection.setDisconnected(null);
- callback.getValue().onComplete(null, true);
- for (Phone phone : mPhoneFactoryProxy.getPhones()) {
- verify(phone).setRadioPower(true, false, false, true);
- }
- }
-
- /**
- * Test that the TelephonyConnectionService successfully turns radio on before placing the
- * emergency call.
- */
- @Test
- @SmallTest
- public void testCreateOutgoingEmergencyConnection_exitingApm_placeCall() {
- when(mDeviceState.isAirplaneModeOn(any())).thenReturn(true);
- Phone testPhone = setupConnectionServiceInApm();
-
- ArgumentCaptor<RadioOnStateListener.Callback> callback =
- ArgumentCaptor.forClass(RadioOnStateListener.Callback.class);
- verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true),
- eq(testPhone), eq(false));
-
- assertFalse(callback.getValue().isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE));
- when(mSST.isRadioOn()).thenReturn(true);
- assertTrue(callback.getValue().isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE));
-
- callback.getValue().onComplete(null, true);
-
- try {
- doAnswer(invocation -> null).when(mContext).startActivity(any());
- verify(testPhone).dial(anyString(), any(), any());
- } catch (CallStateException e) {
- // This shouldn't happen
- fail();
- }
- }
-
- /**
- * Test that the TelephonyConnectionService does not perform a DDS switch when the carrier
- * supports control-plane fallback.
- */
- @Test
- @SmallTest
- public void testCreateOutgoingEmergencyConnection_delayDial_nocarrierconfig() {
- // Setup test to not support SUPL on the non-DDS subscription
- doReturn(true).when(mDeviceState).isSuplDdsSwitchRequiredForEmergencyCall(any());
- getTestContext().getCarrierConfig(0 /*subId*/).putStringArray(
- CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY,
- null);
- getTestContext().getCarrierConfig(0 /*subId*/).putInt(
- CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
- CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_FALLBACK);
- getTestContext().getCarrierConfig(0 /*subId*/).putString(
- CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "0");
-
- Phone testPhone = setupConnectionServiceForDelayDial(
- false /* isRoaming */, false /* setOperatorName */, null /* operator long name*/,
- null /* operator short name */, null /* operator numeric name */);
- verify(mPhoneSwitcher, never()).overrideDefaultDataForEmergency(anyInt(), anyInt(), any());
- }
-
- /**
- * Test that the TelephonyConnectionService does not perform a DDS switch when the carrier
- * supports control-plane fallback.
- */
- @Test
- @SmallTest
- public void testCreateOutgoingEmergencyConnection_delayDial_supportsuplondds() {
- // If the non-DDS supports SUPL, dont switch data
- doReturn(false).when(mDeviceState).isSuplDdsSwitchRequiredForEmergencyCall(any());
- getTestContext().getCarrierConfig(0 /*subId*/).putStringArray(
- CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY,
- null);
- getTestContext().getCarrierConfig(0 /*subId*/).putInt(
- CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
- CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY);
- getTestContext().getCarrierConfig(0 /*subId*/).putString(
- CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "0");
-
- Phone testPhone = setupConnectionServiceForDelayDial(
- false /* isRoaming */, false /* setOperatorName */, null /* operator long name*/,
- null /* operator short name */, null /* operator numeric name */);
- verify(mPhoneSwitcher, never()).overrideDefaultDataForEmergency(anyInt(), anyInt(), any());
- }
-
- /**
- * Test that the TelephonyConnectionService does not perform a DDS switch when the carrier does
- * not support control-plane fallback CarrierConfig while roaming.
- */
- @Test
- @SmallTest
- public void testCreateOutgoingEmergencyConnection_delayDial_roaming_nocarrierconfig() {
- // Setup test to not support SUPL on the non-DDS subscription
- doReturn(true).when(mDeviceState).isSuplDdsSwitchRequiredForEmergencyCall(any());
- getTestContext().getCarrierConfig(0 /*subId*/).putStringArray(
- CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY,
- null);
- getTestContext().getCarrierConfig(0 /*subId*/).putInt(
- CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
- CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY);
- getTestContext().getCarrierConfig(0 /*subId*/).putString(
- CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "0");
-
- Phone testPhone = setupConnectionServiceForDelayDial(
- true /* isRoaming */, false /* setOperatorName */, null /* operator long name*/,
- null /* operator short name */, null /* operator numeric name */);
- verify(mPhoneSwitcher, never()).overrideDefaultDataForEmergency(anyInt(), anyInt(), any());
- }
-
- /**
- * Test that the TelephonyConnectionService does perform a DDS switch even though the carrier
- * supports control-plane fallback CarrierConfig and the roaming partner is configured to look
- * like a home network.
- */
- @Test
- @SmallTest
- public void testCreateOutgoingEmergencyConnection_delayDial_roamingcarrierconfig() {
- doReturn(true).when(mDeviceState).isSuplDdsSwitchRequiredForEmergencyCall(any());
- // Setup voice roaming scenario
- String testRoamingOperator = "001001";
- // Setup test to not support SUPL on the non-DDS subscription
- String[] roamingPlmns = new String[1];
- roamingPlmns[0] = testRoamingOperator;
- getTestContext().getCarrierConfig(0 /*subId*/).putStringArray(
- CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY,
- roamingPlmns);
- getTestContext().getCarrierConfig(0 /*subId*/).putInt(
- CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
- CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_FALLBACK);
- getTestContext().getCarrierConfig(0 /*subId*/).putString(
- CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "0");
-
- Phone testPhone = setupConnectionServiceForDelayDial(
- false /* isRoaming */, true /* setOperatorName */,
- "TestTel" /* operator long name*/, "TestTel" /* operator short name */,
- testRoamingOperator /* operator numeric name */);
- verify(mPhoneSwitcher).overrideDefaultDataForEmergency(eq(0) /*phoneId*/ ,
- eq(0) /*extensionTime*/, any());
- }
-
- /**
- * Test that the TelephonyConnectionService does perform a DDS switch even though the carrier
- * supports control-plane fallback CarrierConfig if we are roaming and the roaming partner is
- * configured to use data plane only SUPL.
- */
- @Test
- @SmallTest
- public void testCreateOutgoingEmergencyConnection_delayDial__roaming_roamingcarrierconfig() {
- // Setup test to not support SUPL on the non-DDS subscription
- doReturn(true).when(mDeviceState).isSuplDdsSwitchRequiredForEmergencyCall(any());
- // Setup voice roaming scenario
- String testRoamingOperator = "001001";
- String[] roamingPlmns = new String[1];
- roamingPlmns[0] = testRoamingOperator;
- getTestContext().getCarrierConfig(0 /*subId*/).putStringArray(
- CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY,
- roamingPlmns);
- getTestContext().getCarrierConfig(0 /*subId*/).putInt(
- CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
- CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_FALLBACK);
- getTestContext().getCarrierConfig(0 /*subId*/).putString(
- CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "0");
-
- Phone testPhone = setupConnectionServiceForDelayDial(
- false /* isRoaming */, true /* setOperatorName */,
- "TestTel" /* operator long name*/, "TestTel" /* operator short name */,
- testRoamingOperator /* operator numeric name */);
- verify(mPhoneSwitcher).overrideDefaultDataForEmergency(eq(0) /*phoneId*/ ,
- eq(0) /*extensionTime*/, any());
- }
-
- /**
* Verifies for an incoming call on the same SIM that we don't set
* {@link android.telecom.Connection#EXTRA_ANSWERING_DROPS_FG_CALL} on the incoming call extras.
* @throws Exception
@@ -1545,6 +1368,277 @@
.isCurrentEmergencyNumber(TEST_ADDRESS.getSchemeSpecificPart());
}
+ @Test
+ public void testDomainSelectionCs() throws Exception {
+ setupForCallTest();
+
+ int selectedDomain = DOMAIN_CS;
+
+ setupForDialForDomainSelection(mPhone0, selectedDomain, true);
+
+ mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+ createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
+ TEST_EMERGENCY_NUMBER, TELECOM_CALL_ID1));
+
+ verify(mDomainSelectionResolver)
+ .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
+ verify(mEmergencyStateTracker)
+ .startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
+ verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
+
+ ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
+
+ verify(mPhone0).dial(anyString(), argsCaptor.capture(), any());
+ DialArgs dialArgs = argsCaptor.getValue();
+ assertNotNull("DialArgs param is null", dialArgs);
+ assertNotNull("intentExtras is null", dialArgs.intentExtras);
+ assertTrue(dialArgs.intentExtras.containsKey(PhoneConstants.EXTRA_DIAL_DOMAIN));
+ assertEquals(selectedDomain,
+ dialArgs.intentExtras.getInt(PhoneConstants.EXTRA_DIAL_DOMAIN, -1));
+ }
+
+ @Test
+ public void testDomainSelectionPs() throws Exception {
+ setupForCallTest();
+
+ int selectedDomain = DOMAIN_PS;
+
+ setupForDialForDomainSelection(mPhone0, selectedDomain, true);
+
+ mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+ createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
+ TEST_EMERGENCY_NUMBER, TELECOM_CALL_ID1));
+
+ verify(mDomainSelectionResolver)
+ .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
+ verify(mEmergencyStateTracker)
+ .startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
+ verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
+
+ ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
+
+ verify(mPhone0).dial(anyString(), argsCaptor.capture(), any());
+ DialArgs dialArgs = argsCaptor.getValue();
+ assertNotNull("DialArgs param is null", dialArgs);
+ assertNotNull("intentExtras is null", dialArgs.intentExtras);
+ assertTrue(dialArgs.intentExtras.containsKey(PhoneConstants.EXTRA_DIAL_DOMAIN));
+ assertEquals(selectedDomain,
+ dialArgs.intentExtras.getInt(PhoneConstants.EXTRA_DIAL_DOMAIN, -1));
+ }
+
+ @Test
+ public void testDomainSelectionCsForTty() throws Exception {
+ setupForCallTest();
+
+ ImsManager imsManager = Mockito.mock(ImsManager.class);
+ doReturn(false).when(imsManager).isNonTtyOrTtyOnVolteEnabled();
+ replaceInstance(TelephonyConnectionService.class,
+ "mImsManager", mTestConnectionService, imsManager);
+
+ setupForDialForDomainSelection(mPhone0, DOMAIN_PS, true);
+
+ mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+ createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
+ TEST_EMERGENCY_NUMBER, TELECOM_CALL_ID1));
+
+ verify(mEmergencyStateTracker, times(1))
+ .startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
+ verify(mDomainSelectionResolver, times(0))
+ .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
+ verify(mEmergencyCallDomainSelectionConnection, times(0))
+ .createEmergencyConnection(any(), any());
+
+ ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
+
+ verify(mPhone0).dial(anyString(), argsCaptor.capture(), any());
+ DialArgs dialArgs = argsCaptor.getValue();
+ assertNotNull("DialArgs param is null", dialArgs);
+ assertNotNull("intentExtras is null", dialArgs.intentExtras);
+ assertTrue(dialArgs.intentExtras.containsKey(PhoneConstants.EXTRA_DIAL_DOMAIN));
+ assertEquals(DOMAIN_CS, dialArgs.intentExtras.getInt(PhoneConstants.EXTRA_DIAL_DOMAIN, -1));
+ }
+
+ @Test
+ public void testDomainSelectionRedialCs() throws Exception {
+ setupForCallTest();
+
+ int preciseDisconnectCause = com.android.internal.telephony.CallFailCause.ERROR_UNSPECIFIED;
+ int disconnectCause = android.telephony.DisconnectCause.ERROR_UNSPECIFIED;
+ int selectedDomain = DOMAIN_CS;
+
+ TestTelephonyConnection c = setupForReDialForDomainSelection(
+ mPhone0, selectedDomain, preciseDisconnectCause, disconnectCause, true);
+
+ assertTrue(mTestConnectionService.maybeReselectDomain(c, preciseDisconnectCause, null));
+ verify(mEmergencyCallDomainSelectionConnection).reselectDomain(any());
+
+ ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
+
+ Connection nc = Mockito.mock(Connection.class);
+ doReturn(nc).when(mPhone0).dial(anyString(), any(), any());
+
+ verify(mPhone0).dial(anyString(), argsCaptor.capture(), any());
+ DialArgs dialArgs = argsCaptor.getValue();
+ assertNotNull("DialArgs param is null", dialArgs);
+ assertNotNull("intentExtras is null", dialArgs.intentExtras);
+ assertTrue(dialArgs.intentExtras.containsKey(PhoneConstants.EXTRA_DIAL_DOMAIN));
+ assertEquals(selectedDomain,
+ dialArgs.intentExtras.getInt(PhoneConstants.EXTRA_DIAL_DOMAIN, -1));
+ }
+
+ @Test
+ public void testDomainSelectionRedialPs() throws Exception {
+ setupForCallTest();
+
+ int preciseDisconnectCause = com.android.internal.telephony.CallFailCause.ERROR_UNSPECIFIED;
+ int disconnectCause = android.telephony.DisconnectCause.ERROR_UNSPECIFIED;
+ int selectedDomain = DOMAIN_PS;
+
+ TestTelephonyConnection c = setupForReDialForDomainSelection(
+ mPhone0, selectedDomain, preciseDisconnectCause, disconnectCause, true);
+
+ assertTrue(mTestConnectionService.maybeReselectDomain(c, preciseDisconnectCause, null));
+ verify(mEmergencyCallDomainSelectionConnection).reselectDomain(any());
+
+ ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
+
+ Connection nc = Mockito.mock(Connection.class);
+ doReturn(nc).when(mPhone0).dial(anyString(), any(), any());
+
+ verify(mPhone0).dial(anyString(), argsCaptor.capture(), any());
+ DialArgs dialArgs = argsCaptor.getValue();
+ assertNotNull("DialArgs param is null", dialArgs);
+ assertNotNull("intentExtras is null", dialArgs.intentExtras);
+ assertTrue(dialArgs.intentExtras.containsKey(PhoneConstants.EXTRA_DIAL_DOMAIN));
+ assertEquals(selectedDomain,
+ dialArgs.intentExtras.getInt(PhoneConstants.EXTRA_DIAL_DOMAIN, -1));
+ }
+
+ @Test
+ public void testDomainSelectionNormalToEmergencyCs() throws Exception {
+ setupForCallTest();
+
+ int preciseDisconnectCause = com.android.internal.telephony.CallFailCause.ERROR_UNSPECIFIED;
+ int disconnectCause = android.telephony.DisconnectCause.ERROR_UNSPECIFIED;
+ int eccCategory = EMERGENCY_SERVICE_CATEGORY_POLICE;
+ int selectedDomain = DOMAIN_CS;
+
+ setupForDialForDomainSelection(mPhone0, selectedDomain, true);
+
+ TestTelephonyConnection c = setupForReDialForDomainSelection(
+ mPhone0, selectedDomain, preciseDisconnectCause, disconnectCause, false);
+ c.setEmergencyServiceCategory(eccCategory);
+ c.setAddress(TEST_ADDRESS, TelecomManager.PRESENTATION_ALLOWED);
+
+ ImsReasonInfo reasonInfo = new ImsReasonInfo(CODE_SIP_ALTERNATE_EMERGENCY_CALL, 0, null);
+ assertTrue(mTestConnectionService.maybeReselectDomain(c,
+ preciseDisconnectCause, reasonInfo));
+
+ verify(mDomainSelectionResolver)
+ .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
+ verify(mEmergencyStateTracker)
+ .startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
+ verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
+
+ ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
+
+ verify(mPhone0).dial(anyString(), argsCaptor.capture(), any());
+ DialArgs dialArgs = argsCaptor.getValue();
+ assertNotNull("DialArgs param is null", dialArgs);
+ assertNotNull("intentExtras is null", dialArgs.intentExtras);
+ assertTrue(dialArgs.intentExtras.containsKey(PhoneConstants.EXTRA_DIAL_DOMAIN));
+ assertEquals(selectedDomain,
+ dialArgs.intentExtras.getInt(PhoneConstants.EXTRA_DIAL_DOMAIN, -1));
+ assertTrue(dialArgs.isEmergency);
+ assertEquals(eccCategory, dialArgs.eccCategory);
+ }
+
+ @Test
+ public void testDomainSelectionNormalToEmergencyPs() throws Exception {
+ setupForCallTest();
+
+ int preciseDisconnectCause = com.android.internal.telephony.CallFailCause.ERROR_UNSPECIFIED;
+ int disconnectCause = android.telephony.DisconnectCause.ERROR_UNSPECIFIED;
+ int eccCategory = EMERGENCY_SERVICE_CATEGORY_POLICE;
+ int selectedDomain = DOMAIN_PS;
+
+ setupForDialForDomainSelection(mPhone0, selectedDomain, true);
+
+ TestTelephonyConnection c = setupForReDialForDomainSelection(
+ mPhone0, selectedDomain, preciseDisconnectCause, disconnectCause, false);
+ c.setEmergencyServiceCategory(eccCategory);
+ c.setAddress(TEST_ADDRESS, TelecomManager.PRESENTATION_ALLOWED);
+
+ ImsReasonInfo reasonInfo = new ImsReasonInfo(CODE_SIP_ALTERNATE_EMERGENCY_CALL, 0, null);
+ assertTrue(mTestConnectionService.maybeReselectDomain(c,
+ preciseDisconnectCause, reasonInfo));
+
+ verify(mDomainSelectionResolver)
+ .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
+ verify(mEmergencyStateTracker)
+ .startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
+ verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
+
+ ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
+
+ verify(mPhone0).dial(anyString(), argsCaptor.capture(), any());
+ DialArgs dialArgs = argsCaptor.getValue();
+ assertNotNull("DialArgs param is null", dialArgs);
+ assertNotNull("intentExtras is null", dialArgs.intentExtras);
+ assertTrue(dialArgs.intentExtras.containsKey(PhoneConstants.EXTRA_DIAL_DOMAIN));
+ assertEquals(selectedDomain,
+ dialArgs.intentExtras.getInt(PhoneConstants.EXTRA_DIAL_DOMAIN, -1));
+ assertTrue(dialArgs.isEmergency);
+ assertEquals(eccCategory, dialArgs.eccCategory);
+ }
+
+ private void setupForDialForDomainSelection(Phone mockPhone, int domain, boolean isEmergency) {
+ if (isEmergency) {
+ doReturn(mEmergencyCallDomainSelectionConnection).when(mDomainSelectionResolver)
+ .getDomainSelectionConnection(any(), anyInt(), eq(true));
+ doReturn(CompletableFuture.completedFuture(domain))
+ .when(mEmergencyCallDomainSelectionConnection)
+ .createEmergencyConnection(any(), any());
+ doReturn(true).when(mTelephonyManagerProxy).isCurrentEmergencyNumber(anyString());
+ }
+
+ doReturn(true).when(mDomainSelectionResolver).isDomainSelectionSupported();
+ doReturn(mImsPhone).when(mockPhone).getImsPhone();
+ }
+
+ private TestTelephonyConnection setupForReDialForDomainSelection(
+ Phone mockPhone, int domain, int preciseDisconnectCause,
+ int disconnectCause, boolean fromEmergency) throws Exception {
+ try {
+ if (fromEmergency) {
+ doReturn(CompletableFuture.completedFuture(domain))
+ .when(mEmergencyCallDomainSelectionConnection)
+ .reselectDomain(any());
+ replaceInstance(TelephonyConnectionService.class,
+ "mEmergencyCallDomainSelectionConnection",
+ mTestConnectionService, mEmergencyCallDomainSelectionConnection);
+ replaceInstance(TelephonyConnectionService.class, "mEmergencyCallId",
+ mTestConnectionService, TELECOM_CALL_ID1);
+ }
+ } catch (Exception e) {
+ // This shouldn't happen
+ fail();
+ }
+
+ doReturn(true).when(mDomainSelectionResolver).isDomainSelectionSupported();
+
+ TestTelephonyConnection c = new TestTelephonyConnection();
+ c.setTelecomCallId(TELECOM_CALL_ID1);
+ c.setMockPhone(mockPhone);
+ c.setAddress(TEST_ADDRESS, TelecomManager.PRESENTATION_ALLOWED);
+
+ Connection oc = c.getOriginalConnection();
+ doReturn(disconnectCause).when(oc).getDisconnectCause();
+ doReturn(preciseDisconnectCause).when(oc).getPreciseDisconnectCause();
+
+ return c;
+ }
+
private SimpleTelephonyConnection createTestConnection(PhoneAccountHandle handle,
int properties, boolean isEmergency) {
SimpleTelephonyConnection connection = new SimpleTelephonyConnection();
@@ -1760,4 +1854,18 @@
fail();
}
}
+
+ private ConnectionRequest createConnectionRequest(
+ PhoneAccountHandle accountHandle, String address, String callId) {
+ return new ConnectionRequest.Builder()
+ .setAccountHandle(accountHandle)
+ .setAddress(Uri.parse("tel:" + address))
+ .setExtras(new Bundle())
+ .setTelecomCallId(callId)
+ .build();
+ }
+
+ private Executor getExecutor() {
+ return Runnable::run;
+ }
}
diff --git a/tests/src/com/android/services/telephony/TelephonyConnectionTest.java b/tests/src/com/android/services/telephony/TelephonyConnectionTest.java
index c996e5f..3c309ba 100644
--- a/tests/src/com/android/services/telephony/TelephonyConnectionTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConnectionTest.java
@@ -1,5 +1,7 @@
package com.android.services.telephony;
+import static android.telecom.Connection.STATE_DISCONNECTED;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
@@ -7,6 +9,9 @@
import static junit.framework.Assert.fail;
import static junit.framework.TestCase.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -26,7 +31,6 @@
import com.android.internal.telephony.d2d.DtmfTransport;
import com.android.internal.telephony.d2d.RtpTransport;
import com.android.internal.telephony.imsphone.ImsPhoneConnection;
-import com.android.phone.PhoneGlobals;
import com.android.phone.R;
import org.junit.Before;
@@ -39,6 +43,8 @@
public class TelephonyConnectionTest {
@Mock
private ImsPhoneConnection mImsPhoneConnection;
+ @Mock
+ private TelephonyConnectionService mTelephonyConnectionService;
@Before
public void setUp() throws Exception {
@@ -257,4 +263,44 @@
assertTrue(c.isRttMergeSupported(c.getCarrierConfig()));
}
+ @Test
+ public void testDomainSelectionDisconnected() {
+ TestTelephonyConnection c = new TestTelephonyConnection();
+ c.setOriginalConnection(mImsPhoneConnection);
+ doReturn(Call.State.DISCONNECTED).when(mImsPhoneConnection)
+ .getState();
+ c.setTelephonyConnectionService(mTelephonyConnectionService);
+ c.updateState();
+
+ verify(mTelephonyConnectionService)
+ .maybeReselectDomain(any(), anyInt(), any());
+ }
+
+ @Test
+ public void testDomainSelectionDisconnected_NoRedial() {
+ TestTelephonyConnection c = new TestTelephonyConnection();
+ c.setOriginalConnection(mImsPhoneConnection);
+ doReturn(Call.State.DISCONNECTED).when(mImsPhoneConnection)
+ .getState();
+ c.setTelephonyConnectionService(mTelephonyConnectionService);
+ doReturn(false).when(mTelephonyConnectionService)
+ .maybeReselectDomain(any(), anyInt(), any());
+ c.updateState();
+
+ assertEquals(STATE_DISCONNECTED, c.getState());
+ }
+
+ @Test
+ public void testDomainSelectionDisconnected_Redial() {
+ TestTelephonyConnection c = new TestTelephonyConnection();
+ c.setOriginalConnection(mImsPhoneConnection);
+ doReturn(Call.State.DISCONNECTED).when(mImsPhoneConnection)
+ .getState();
+ c.setTelephonyConnectionService(mTelephonyConnectionService);
+ doReturn(true).when(mTelephonyConnectionService)
+ .maybeReselectDomain(any(), anyInt(), any());
+ c.updateState();
+
+ assertNotEquals(STATE_DISCONNECTED, c.getState());
+ }
}