Switch DDS on MSIM emergency call
1) Wait for DDS switch before placing emergency calls
in certain situations.
2) Make sure the subscription that is the current
DDS subscription is the one used to place the emergency
call.
Test: atest FrameworksTelephonyTests
Merged-In: I09790ed3c15409356e34d259226a59f7107cd126
Change-Id: I09790ed3c15409356e34d259226a59f7107cd126
(cherry picked from commit 92fc18daba2272dd0bad322fda9ab3aa4d73f14f)
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index b2fb3ad..b12fc06 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -273,7 +273,7 @@
capabilities |= PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS;
}
- mIsEmergencyPreferred = isEmergencyPreferredAccount(subId);
+ mIsEmergencyPreferred = isEmergencyPreferredAccount(subId, mActiveDataSubscriptionId);
if (mIsEmergencyPreferred) {
capabilities |= PhoneAccount.CAPABILITY_EMERGENCY_PREFERRED;
}
@@ -426,24 +426,47 @@
* @return true if Telecom should prefer this PhoneAccount, false if there is no preference
* needed.
*/
- private boolean isEmergencyPreferredAccount(int subId) {
+ private boolean isEmergencyPreferredAccount(int subId, int activeDataSubId) {
+ Log.d(this, "isEmergencyPreferredAccount: subId=" + subId + ", activeData="
+ + activeDataSubId);
final boolean gnssSuplRequiresDefaultData = mContext.getResources().getBoolean(
R.bool.config_gnss_supl_requires_default_data_for_emergency);
if (!gnssSuplRequiresDefaultData) {
+ Log.d(this, "isEmergencyPreferredAccount: Device does not require preference.");
// No preference is necessary.
return false;
}
- // Only set a preference on MSIM devices
- if (mTelephonyManager.getPhoneCount() <= 1) {
+ SubscriptionController controller = SubscriptionController.getInstance();
+ if (controller == null) {
+ Log.d(this, "isEmergencyPreferredAccount: SubscriptionController not available.");
+ return false;
+ }
+ // Only set an emergency preference on devices with multiple active subscriptions
+ // (include opportunistic subscriptions) in this check.
+ // API says never null, but this can return null in testing.
+ int[] activeSubIds = controller.getActiveSubIdList(false);
+ if (activeSubIds == null || activeSubIds.length <= 1) {
+ Log.d(this, "isEmergencyPreferredAccount: one or less active subscriptions.");
return false;
}
// Check to see if this PhoneAccount is associated with the default Data subscription.
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ Log.d(this, "isEmergencyPreferredAccount: provided subId " + subId + "is not "
+ + "valid.");
return false;
}
- SubscriptionController controller = SubscriptionController.getInstance();
- return controller != null && (controller.getDefaultDataSubId() == subId);
+ int userDefaultData = controller.getDefaultDataSubId();
+ boolean isActiveDataValid = SubscriptionManager.isValidSubscriptionId(activeDataSubId);
+ boolean isActiveDataOpportunistic = isActiveDataValid
+ && controller.isOpportunistic(activeDataSubId);
+ // compare the activeDataSubId to the subId specified only if it is valid and not an
+ // opportunistic subscription (only supports data). If not, use the current default
+ // defined by the user.
+ Log.d(this, "isEmergencyPreferredAccount: userDefaultData=" + userDefaultData
+ + ", isActiveDataOppurtunistic=" + isActiveDataOpportunistic);
+ return subId == ((isActiveDataValid && !isActiveDataOpportunistic) ? activeDataSubId :
+ userDefaultData);
}
/**
@@ -633,8 +656,9 @@
}
}
- public void updateDefaultDataSubId() {
- boolean isEmergencyPreferred = isEmergencyPreferredAccount(mPhone.getSubId());
+ public void updateDefaultDataSubId(int activeDataSubId) {
+ boolean isEmergencyPreferred = isEmergencyPreferredAccount(mPhone.getSubId(),
+ activeDataSubId);
if (isEmergencyPreferred != mIsEmergencyPreferred) {
Log.i(this, "updateDefaultDataSubId - changed, new value: " + isEmergencyPreferred);
mAccount = registerPstnPhoneAccount(mIsEmergency, mIsDummy);
@@ -790,9 +814,10 @@
@Override
public void onActiveDataSubscriptionIdChanged(int subId) {
+ mActiveDataSubscriptionId = subId;
synchronized (mAccountsLock) {
for (AccountEntry account : mAccounts) {
- account.updateDefaultDataSubId();
+ account.updateDefaultDataSubId(mActiveDataSubscriptionId);
}
}
}
@@ -806,6 +831,7 @@
private List<AccountEntry> mAccounts = new LinkedList<AccountEntry>();
private final Object mAccountsLock = new Object();
private int mServiceState = ServiceState.STATE_POWER_OFF;
+ private int mActiveDataSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private boolean mIsPrimaryUser = true;
// TODO: Remove back-pointer from app singleton to Service, since this is not a preferred
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 4b29f4a..927b2c9 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 android.annotation.NonNull;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -53,6 +54,7 @@
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.PhoneSwitcher;
import com.android.internal.telephony.RIL;
import com.android.internal.telephony.SubscriptionController;
import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
@@ -71,6 +73,8 @@
import java.util.List;
import java.util.Map;
import java.util.Queue;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
@@ -80,6 +84,10 @@
*/
public class TelephonyConnectionService extends ConnectionService {
+ // Timeout before we continue with the emergency call without waiting for DDS switch response
+ // from the modem.
+ private static final int DEFAULT_DATA_SWITCH_TIMEOUT_MS = 1000;
+
// If configured, reject attempts to dial numbers matching this pattern.
private static final Pattern CDMA_ACTIVATION_CODE_REGEX_PATTERN =
Pattern.compile("\\*228[0-9]{0,2}");
@@ -508,21 +516,41 @@
final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber,
/* Note: when not an emergency, handle can be null for unknown callers */
handle == null ? null : handle.getSchemeSpecificPart());
- Connection resultConnection = getTelephonyConnection(request, numberToDial,
- isEmergencyNumber, handle, phone);
- // If there was a failure, the resulting connection will not be a TelephonyConnection,
- // so don't place the call!
- if (resultConnection instanceof TelephonyConnection) {
- if (request.getExtras() != null && request.getExtras().getBoolean(
- TelecomManager.EXTRA_USE_ASSISTED_DIALING, false)) {
- ((TelephonyConnection) resultConnection).setIsUsingAssistedDialing(true);
- }
- placeOutgoingConnection((TelephonyConnection) resultConnection, phone, request);
+ if (!isEmergencyNumber) {
+ final Connection resultConnection = getTelephonyConnection(request, numberToDial,
+ false, handle, phone);
+ return placeOutgoingConnection(request, resultConnection, phone);
+ } else {
+ final Connection resultConnection = getTelephonyConnection(request, numberToDial,
+ true, handle, phone);
+ CompletableFuture<Boolean> phoneFuture = delayDialForDdsSwitch(phone);
+ phoneFuture.whenComplete((result, error) -> {
+ if (error != null) {
+ Log.w(this, "onCreateOutgoingConn - delayDialForDdsSwitch exception= "
+ + error.getMessage());
+ }
+ Log.i(this, "onCreateOutgoingConn - delayDialForDdsSwitch result = " + result);
+ placeOutgoingConnection(request, resultConnection, phone);
+ });
+ return resultConnection;
}
- return resultConnection;
}
}
+ private Connection placeOutgoingConnection(ConnectionRequest request,
+ Connection resultConnection, Phone phone) {
+ // If there was a failure, the resulting connection will not be a TelephonyConnection,
+ // so don't place the call!
+ if (resultConnection instanceof TelephonyConnection) {
+ if (request.getExtras() != null && request.getExtras().getBoolean(
+ TelecomManager.EXTRA_USE_ASSISTED_DIALING, false)) {
+ ((TelephonyConnection) resultConnection).setIsUsingAssistedDialing(true);
+ }
+ placeOutgoingConnection((TelephonyConnection) resultConnection, phone, request);
+ }
+ return resultConnection;
+ }
+
private boolean isEmergencyNumberTestNumber(String number) {
number = PhoneNumberUtils.stripSeparators(number);
Map<Integer, List<EmergencyNumber>> list =
@@ -567,40 +595,26 @@
+ "placement.");
return;
}
+ // Get the right phone object since the radio has been turned on successfully.
if (isRadioReady) {
- // Get the right phone object since the radio has been turned on
- // successfully.
final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber,
/* Note: when not an emergency, handle can be null for unknown callers */
handle == null ? null : handle.getSchemeSpecificPart());
- // If the PhoneType of the Phone being used is different than the Default Phone, then we
- // need create a new Connection using that PhoneType and replace it in Telecom.
- if (phone.getPhoneType() != originalPhoneType) {
- Connection repConnection = getTelephonyConnection(request, numberToDial,
- isEmergencyNumber, handle, phone);
- // If there was a failure, the resulting connection will not be a
- // TelephonyConnection, so don't place the call, just return!
- if (repConnection instanceof TelephonyConnection) {
- placeOutgoingConnection((TelephonyConnection) repConnection, phone, request);
- }
- // Notify Telecom of the new Connection type.
- // TODO: Switch out the underlying connection instead of creating a new
- // one and causing UI Jank.
- boolean noActiveSimCard = SubscriptionController.getInstance()
- .getActiveSubInfoCount(phone.getContext().getOpPackageName()) == 0;
- // If there's no active sim card and the device is in emergency mode, use E account.
- addExistingConnection(PhoneUtils.makePstnPhoneAccountHandleWithPrefix(
- phone, "", isEmergencyNumber && noActiveSimCard), repConnection);
- // Remove the old connection from Telecom after.
- originalConnection.setDisconnected(
- DisconnectCauseUtil.toTelecomDisconnectCause(
- android.telephony.DisconnectCause.OUTGOING_CANCELED,
- "Reconnecting outgoing Emergency Call.",
- phone.getPhoneId()));
- originalConnection.destroy();
+ if (!isEmergencyNumber) {
+ adjustAndPlaceOutgoingConnection(phone, originalConnection, request, numberToDial,
+ handle, originalPhoneType, false);
} else {
- placeOutgoingConnection((TelephonyConnection) originalConnection, phone, request);
+ delayDialForDdsSwitch(phone).whenComplete((result, error) -> {
+ if (error != null) {
+ Log.w(this, "handleOnComplete - delayDialForDdsSwitch exception= "
+ + error.getMessage());
+ }
+ Log.i(this, "handleOnComplete - delayDialForDdsSwitch result = " + result);
+ adjustAndPlaceOutgoingConnection(phone, originalConnection, request,
+ numberToDial, handle, originalPhoneType, true);
+ });
}
+
} else {
Log.w(this, "onCreateOutgoingConnection, failed to turn on radio");
originalConnection.setDisconnected(
@@ -611,6 +625,39 @@
}
}
+ private void adjustAndPlaceOutgoingConnection(Phone phone, Connection connectionToEvaluate,
+ ConnectionRequest request, String numberToDial, Uri handle, int originalPhoneType,
+ boolean isEmergencyNumber) {
+ // If the PhoneType of the Phone being used is different than the Default Phone, then we
+ // need to create a new Connection using that PhoneType and replace it in Telecom.
+ if (phone.getPhoneType() != originalPhoneType) {
+ Connection repConnection = getTelephonyConnection(request, numberToDial,
+ isEmergencyNumber, handle, phone);
+ // If there was a failure, the resulting connection will not be a TelephonyConnection,
+ // so don't place the call, just return!
+ if (repConnection instanceof TelephonyConnection) {
+ placeOutgoingConnection((TelephonyConnection) repConnection, phone, request);
+ }
+ // Notify Telecom of the new Connection type.
+ // TODO: Switch out the underlying connection instead of creating a new
+ // one and causing UI Jank.
+ boolean noActiveSimCard = SubscriptionController.getInstance()
+ .getActiveSubInfoCount(phone.getContext().getOpPackageName()) == 0;
+ // If there's no active sim card and the device is in emergency mode, use E account.
+ addExistingConnection(PhoneUtils.makePstnPhoneAccountHandleWithPrefix(
+ phone, "", isEmergencyNumber && noActiveSimCard), repConnection);
+ // Remove the old connection from Telecom after.
+ connectionToEvaluate.setDisconnected(
+ DisconnectCauseUtil.toTelecomDisconnectCause(
+ android.telephony.DisconnectCause.OUTGOING_CANCELED,
+ "Reconnecting outgoing Emergency Call.",
+ phone.getPhoneId()));
+ connectionToEvaluate.destroy();
+ } else {
+ placeOutgoingConnection((TelephonyConnection) connectionToEvaluate, phone, request);
+ }
+ }
+
/**
* @return {@code true} if any other call is disabling the ability to add calls, {@code false}
* otherwise.
@@ -1318,8 +1365,7 @@
// If this is an emergency call and the phone we originally planned to make this call
// with is not in service or was invalid, try to find one that is in service, using the
// default as a last chance backup.
- if (isEmergency && (chosenPhone == null || ServiceState.STATE_IN_SERVICE != chosenPhone
- .getServiceState().getState())) {
+ if (isEmergency && (chosenPhone == null || !isAvailableForEmergencyCalls(chosenPhone))) {
Log.d(this, "getPhoneForAccount: phone for phone acct handle %s is out of service "
+ "or invalid for emergency call.", accountHandle);
chosenPhone = getPhoneForEmergencyCall(emergencyNumberAddress);
@@ -1329,6 +1375,98 @@
return chosenPhone;
}
+ private CompletableFuture<Boolean> delayDialForDdsSwitch(Phone phone) {
+ if (phone == null) {
+ return CompletableFuture.completedFuture(Boolean.TRUE);
+ }
+ return possiblyOverrideDefaultDataForEmergencyCall(phone)
+ .completeOnTimeout(false, DEFAULT_DATA_SWITCH_TIMEOUT_MS,
+ TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * If needed, block until Default Data subscription is switched for outgoing emergency call.
+ *
+ * In some cases, we need to try to switch the Default Data subscription before placing the
+ * emergency call on DSDS devices. This includes the following situation:
+ * - The modem does not support processing GNSS SUPL requests on the non-default data
+ * subscription. For some carriers that do not provide a control plane fallback mechanism, the
+ * SUPL request will be dropped and we will not be able to get the user's location for the
+ * emergency call. In this case, we need to swap default data temporarily.
+ * @param phone Evaluates whether or not the default data should be moved to the phone
+ * specified. Should not be null.
+ */
+ private CompletableFuture<Boolean> possiblyOverrideDefaultDataForEmergencyCall(
+ @NonNull Phone phone) {
+ TelephonyManager telephony = TelephonyManager.from(phone.getContext());
+ int phoneCount = telephony.getPhoneCount();
+ // Do not override DDS if this is a single SIM device.
+ if (phoneCount <= PhoneConstants.MAX_PHONE_COUNT_SINGLE_SIM) {
+ return CompletableFuture.completedFuture(Boolean.TRUE);
+ }
+
+ CarrierConfigManager cfgManager = (CarrierConfigManager)
+ phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ if (cfgManager == null) {
+ // For some reason CarrierConfigManager is unavailable. Do not block emergency call.
+ Log.w(this, "possiblyOverrideDefaultDataForEmergencyCall: couldn't get"
+ + "CarrierConfigManager");
+ return CompletableFuture.completedFuture(Boolean.TRUE);
+ }
+ // Only override default data if we are IN_SERVICE and on a home network. We don't want to
+ // perform a DDS switch of we are on a roaming network, where SUPL may not be available.
+ boolean isPhoneAvailableForEmergency = isAvailableForEmergencyCalls(phone);
+ boolean isRoaming = phone.getServiceState().getVoiceRoaming();
+ if (!isPhoneAvailableForEmergency || isRoaming) {
+ Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS, avail = "
+ + isPhoneAvailableForEmergency + ", roaming = " + isRoaming);
+ return CompletableFuture.completedFuture(Boolean.TRUE);
+ }
+
+ // Do not switch Default data if this device supports emergency SUPL on non-DDS.
+ final boolean gnssSuplRequiresDefaultData = phone.getContext().getResources().getBoolean(
+ R.bool.config_gnss_supl_requires_default_data_for_emergency);
+ if (!gnssSuplRequiresDefaultData) {
+ Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS, does not "
+ + "require DDS switch.");
+ return CompletableFuture.completedFuture(Boolean.TRUE);
+ }
+
+ final boolean supportsCpFallback = cfgManager.getConfigForSubId(phone.getSubId())
+ .getInt(CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
+ CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_ONLY)
+ != CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY;
+ if (supportsCpFallback) {
+ Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS, carrier "
+ + "supports CP fallback.");
+ // Do not try to swap default data if we support CS fallback, do not want to introduce
+ // a lag in emergency call setup time if possible.
+ return CompletableFuture.completedFuture(Boolean.TRUE);
+ }
+
+ // Get extension time, may be 0 for some carriers that support ECBM as well. Use
+ // CarrierConfig default if format fails.
+ int extensionTime = 0;
+ try {
+ extensionTime = Integer.valueOf(cfgManager.getConfigForSubId(phone.getSubId())
+ .getString(CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "0"));
+ } catch (NumberFormatException e) {
+ // Just use default.
+ }
+ CompletableFuture<Boolean> modemResultFuture = new CompletableFuture<>();
+ try {
+ Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: overriding DDS for "
+ + extensionTime + "seconds");
+ PhoneSwitcher.getInstance().overrideDefaultDataForEmergency(phone.getPhoneId(),
+ extensionTime, modemResultFuture);
+ // Catch all exceptions, we want to continue with emergency call if possible.
+ } catch (Exception e) {
+ Log.w(this, "possiblyOverrideDefaultDataForEmergencyCall: exception = "
+ + e.getMessage());
+ }
+ return modemResultFuture;
+ }
+
/**
* Get the Phone to use for an emergency call of the given emergency number address:
* a) If there are multiple Phones with the Subscriptions that support the emergency number