Merge "Switch DDS on MSIM emergency call" into qt-dev
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index 46f7023..6bbcdc4 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -274,7 +274,7 @@
capabilities |= PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS;
}
- mIsEmergencyPreferred = isEmergencyPreferredAccount(subId);
+ mIsEmergencyPreferred = isEmergencyPreferredAccount(subId, mActiveDataSubscriptionId);
if (mIsEmergencyPreferred) {
capabilities |= PhoneAccount.CAPABILITY_EMERGENCY_PREFERRED;
}
@@ -428,24 +428,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);
}
/**
@@ -648,8 +671,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);
@@ -815,9 +839,10 @@
@Override
public void onActiveDataSubscriptionIdChanged(int subId) {
+ mActiveDataSubscriptionId = subId;
synchronized (mAccountsLock) {
for (AccountEntry account : mAccounts) {
- account.updateDefaultDataSubId();
+ account.updateDefaultDataSubId(mActiveDataSubscriptionId);
}
}
}
@@ -831,6 +856,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 498625d..80ba5b1 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.imsphone.ImsExternalCallTracker;
import com.android.internal.telephony.imsphone.ImsPhone;
@@ -70,6 +72,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;
@@ -79,6 +83,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}");
@@ -504,21 +512,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 =
@@ -563,37 +591,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.
- addExistingConnection(PhoneUtils.makePstnPhoneAccountHandleWithPrefix(
- phone, "", isEmergencyNumber), 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(
@@ -604,6 +621,36 @@
}
}
+ 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.
+ addExistingConnection(PhoneUtils.makePstnPhoneAccountHandleWithPrefix(
+ phone, "", isEmergencyNumber), 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.
@@ -1311,8 +1358,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);
@@ -1322,6 +1368,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