Merge "Add text to RTT setting when roaming"
diff --git a/src/com/android/phone/CarrierConfigLoader.java b/src/com/android/phone/CarrierConfigLoader.java
index 8b74af4..4025bbb 100644
--- a/src/com/android/phone/CarrierConfigLoader.java
+++ b/src/com/android/phone/CarrierConfigLoader.java
@@ -20,7 +20,6 @@
import static android.service.carrier.CarrierService.ICarrierServiceWrapper.RESULT_ERROR;
import android.annotation.NonNull;
-import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -40,7 +39,6 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
-import android.os.UserHandle;
import android.preference.PreferenceManager;
import android.service.carrier.CarrierIdentifier;
import android.service.carrier.CarrierService;
@@ -514,7 +512,7 @@
pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
pkgFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
pkgFilter.addDataScheme("package");
- context.registerReceiverAsUser(mPackageReceiver, UserHandle.ALL, pkgFilter, null, null);
+ context.registerReceiver(mPackageReceiver, pkgFilter);
int numPhones = TelephonyManager.from(context).getPhoneCount();
mConfigFromDefaultApp = new PersistableBundle[numPhones];
@@ -592,7 +590,7 @@
}
intent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, phoneId);
log("Broadcast CARRIER_CONFIG_CHANGED for phone " + phoneId);
- ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL);
+ mContext.sendBroadcast(intent);
mHasSentConfigChange[phoneId] = true;
}
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index fed41b0..04850f3 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -527,6 +527,9 @@
maybeTurnCellOn(context, isAirplaneNewlyOn);
break;
}
+ for (Phone phone : PhoneFactory.getPhones()) {
+ phone.getServiceStateTracker().onAirplaneModeChanged(isAirplaneNewlyOn);
+ }
}
/*
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 6c60f1f..5c6dd83 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -1410,7 +1410,7 @@
// from the context of the phone app.
enforceCallPermission();
- if (mAppOps.noteOp(AppOpsManager.OP_CALL_PHONE, Binder.getCallingUid(), callingPackage)
+ if (mAppOps.noteOp(AppOpsManager.OPSTR_CALL_PHONE, Binder.getCallingUid(), callingPackage)
!= AppOpsManager.MODE_ALLOWED) {
return;
}
@@ -2024,14 +2024,18 @@
/**
* Returns the target SDK version number for a given package name.
*
+ * This call MUST be invoked before clearing the calling UID.
+ *
* @return target SDK if the package is found or INT_MAX.
*/
private int getTargetSdk(String packageName) {
try {
- final ApplicationInfo ai = mApp.getPackageManager().getApplicationInfo(
- packageName, 0);
+ final ApplicationInfo ai = mApp.getPackageManager().getApplicationInfoAsUser(
+ packageName, 0, UserHandle.getUserId(Binder.getCallingUid()));
if (ai != null) return ai.targetSdkVersion;
} catch (PackageManager.NameNotFoundException unexpected) {
+ loge("Failed to get package info for pkg="
+ + packageName + ", uid=" + Binder.getCallingUid());
}
return Integer.MAX_VALUE;
}
@@ -2045,7 +2049,7 @@
"getNeighboringCellInfo() is unavailable to callers targeting Q+ SDK levels.");
}
- if (mAppOps.noteOp(AppOpsManager.OP_NEIGHBORING_CELLS, Binder.getCallingUid(),
+ if (mAppOps.noteOp(AppOpsManager.OPSTR_NEIGHBORING_CELLS, Binder.getCallingUid(),
callingPackage) != AppOpsManager.MODE_ALLOWED) {
return null;
}
diff --git a/src/com/android/phone/settings/AccessibilitySettingsFragment.java b/src/com/android/phone/settings/AccessibilitySettingsFragment.java
index 40dfc72..3069091 100644
--- a/src/com/android/phone/settings/AccessibilitySettingsFragment.java
+++ b/src/com/android/phone/settings/AccessibilitySettingsFragment.java
@@ -34,6 +34,7 @@
import com.android.ims.ImsManager;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.SubscriptionController;
import com.android.phone.PhoneGlobals;
import com.android.phone.R;
@@ -110,8 +111,7 @@
mButtonHac = null;
}
- if (PhoneGlobals.getInstance().phoneMgr
- .isRttSupported(SubscriptionManager.getDefaultVoiceSubscriptionId())) {
+ if (shouldShowRttSetting()) {
// TODO: this is going to be a on/off switch for now. Ask UX about how to integrate
// this settings with TTY
if (TelephonyManager.getDefault().isNetworkRoaming(
@@ -198,6 +198,21 @@
return false;
}
+ private boolean shouldShowRttSetting() {
+ int subscriptionId = SubscriptionManager.getDefaultVoiceSubscriptionId();
+ if (subscriptionId == SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ || subscriptionId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
+ for (int subId : SubscriptionController.getInstance().getActiveSubIdList(true)) {
+ if (PhoneGlobals.getInstance().phoneMgr.isRttSupported(subId)) {
+ return true;
+ }
+ }
+ return false;
+ } else {
+ return PhoneGlobals.getInstance().phoneMgr.isRttSupported(subscriptionId);
+ }
+ }
+
/**
* Determines if the device supports TTY per carrier config.
* @return {@code true} if the carrier supports TTY, {@code false} otherwise.
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index 1f157e3..8ef0464 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -259,6 +259,8 @@
private boolean mCouldManageConference;
private FeatureFlagProxy mFeatureFlagProxy;
private boolean mIsEmulatingSinglePartyCall = false;
+ private boolean mIsUsingSimCallManager = false;
+
/**
* Where {@link #mIsEmulatingSinglePartyCall} is {@code true}, contains the
* {@link ConferenceParticipantConnection#getUserEntity()} and
@@ -616,6 +618,7 @@
private void updateManageConference() {
boolean couldManageConference = can(Connection.CAPABILITY_MANAGE_CONFERENCE);
boolean canManageConference = mFeatureFlagProxy.isUsingSinglePartyCallEmulation()
+ && mIsEmulatingSinglePartyCall
? mConferenceParticipantConnections.size() > 1
: mConferenceParticipantConnections.size() != 0;
Log.v(this, "updateManageConference was :%s is:%s", couldManageConference ? "Y" : "N",
@@ -675,6 +678,9 @@
mConferenceHostAddress = new Uri[hostAddresses.size()];
mConferenceHostAddress = hostAddresses.toArray(mConferenceHostAddress);
+
+ mIsUsingSimCallManager = mTelecomAccountRegistry.isUsingSimCallManager(
+ mConferenceHostPhoneAccountHandle);
}
mConferenceHost.addConnectionListener(mConferenceHostListener);
@@ -829,8 +835,18 @@
* 1. Tell telecom we're a conference again.
* 2. Restore {@link Connection#CAPABILITY_MANAGE_CONFERENCE} capability.
* 3. Null out the name/address.
+ *
+ * Note: Single party call emulation is disabled if the conference is taking place via a
+ * sim call manager. Emulating a single party call requires properties of the conference to be
+ * changed (connect time, address, conference state) which cannot be guaranteed to be relayed
+ * correctly by the sim call manager to Telecom.
*/
private void stopEmulatingSinglePartyCall() {
+ if (mIsUsingSimCallManager) {
+ Log.i(this, "stopEmulatingSinglePartyCall: using sim call manager; skip.");
+ return;
+ }
+
Log.i(this, "stopEmulatingSinglePartyCall: conference now has more than one"
+ " participant; make it look conference-like again.");
mIsEmulatingSinglePartyCall = false;
@@ -867,8 +883,18 @@
* 2. Tell telecom we're not a conference.
* 3. Remove {@link Connection#CAPABILITY_MANAGE_CONFERENCE} capability.
* 4. Set the name/address to that of the single participant.
+ *
+ * Note: Single party call emulation is disabled if the conference is taking place via a
+ * sim call manager. Emulating a single party call requires properties of the conference to be
+ * changed (connect time, address, conference state) which cannot be guaranteed to be relayed
+ * correctly by the sim call manager to Telecom.
*/
private void startEmulatingSinglePartyCall() {
+ if (mIsUsingSimCallManager) {
+ Log.i(this, "startEmulatingSinglePartyCall: using sim call manager; skip.");
+ return;
+ }
+
Log.i(this, "startEmulatingSinglePartyCall: conference has a single "
+ "participant; downgrade to single party call.");
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index b2fb3ad..3505fe7 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -98,6 +98,7 @@
private boolean mIsVideoConferencingSupported;
private boolean mIsMergeOfWifiCallsAllowedWhenVoWifiOff;
private boolean mIsManageImsConferenceCallSupported;
+ private boolean mIsUsingSimCallManager;
private boolean mIsShowPreciseFailedCause;
AccountEntry(Phone phone, boolean isEmergency, boolean isDummy) {
@@ -273,7 +274,7 @@
capabilities |= PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS;
}
- mIsEmergencyPreferred = isEmergencyPreferredAccount(subId);
+ mIsEmergencyPreferred = isEmergencyPreferredAccount(subId, mActiveDataSubscriptionId);
if (mIsEmergencyPreferred) {
capabilities |= PhoneAccount.CAPABILITY_EMERGENCY_PREFERRED;
}
@@ -350,6 +351,7 @@
mIsMergeOfWifiCallsAllowedWhenVoWifiOff =
isCarrierMergeOfWifiCallsAllowedWhenVoWifiOff();
mIsManageImsConferenceCallSupported = isCarrierManageImsConferenceCallSupported();
+ mIsUsingSimCallManager = isCarrierUsingSimCallManager();
mIsShowPreciseFailedCause = isCarrierShowPreciseFailedCause();
if (isEmergency && mContext.getResources().getBoolean(
@@ -426,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);
}
/**
@@ -558,6 +583,19 @@
}
/**
+ * Determines from carrier config whether the carrier uses a sim call manager.
+ *
+ * @return {@code true} if the carrier uses a sim call manager,
+ * {@code false} otherwise.
+ */
+ private boolean isCarrierUsingSimCallManager() {
+ PersistableBundle b =
+ PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
+ return !TextUtils.isEmpty(
+ b.getString(CarrierConfigManager.KEY_DEFAULT_SIM_CALL_MANAGER_STRING));
+ }
+
+ /**
* Determines from carrier config whether showing percise call diconnect cause to user
* is supported.
*
@@ -633,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);
@@ -707,6 +746,15 @@
}
/**
+ * Indicates whether this account uses a sim call manger.
+ * @return {@code true} if the account uses a sim call manager,
+ * {@code false} otherwise.
+ */
+ public boolean isUsingSimCallManager() {
+ return mIsUsingSimCallManager;
+ }
+
+ /**
* Indicates whether this account supports showing the precise call disconnect cause
* to user (i.e. conferencing).
* @return {@code true} if the account supports showing the precise call disconnect cause,
@@ -790,9 +838,10 @@
@Override
public void onActiveDataSubscriptionIdChanged(int subId) {
+ mActiveDataSubscriptionId = subId;
synchronized (mAccountsLock) {
for (AccountEntry account : mAccounts) {
- account.updateDefaultDataSubId();
+ account.updateDefaultDataSubId(mActiveDataSubscriptionId);
}
}
}
@@ -806,6 +855,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
@@ -977,7 +1027,7 @@
* @param handle The phone account handle to find the subscription address for.
* @return The address.
*/
- Uri getAddress(PhoneAccountHandle handle) {
+ public Uri getAddress(PhoneAccountHandle handle) {
synchronized (mAccountsLock) {
for (AccountEntry entry : mAccounts) {
if (entry.getPhoneAccountHandle().equals(handle)) {
@@ -989,6 +1039,24 @@
}
/**
+ * Returns whethere a the subscription associated with a {@link PhoneAccountHandle} is using a
+ * sim call manager.
+ *
+ * @param handle The phone account handle to find the subscription address for.
+ * @return {@code true} if a sim call manager is in use, {@code false} otherwise.
+ */
+ public boolean isUsingSimCallManager(PhoneAccountHandle handle) {
+ synchronized (mAccountsLock) {
+ for (AccountEntry entry : mAccounts) {
+ if (entry.getPhoneAccountHandle().equals(handle)) {
+ return entry.isUsingSimCallManager();
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
* Sets up all the phone accounts for SIMs on first boot.
*/
public void setupOnBoot() {
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 632e9ac..7bb6b7d 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -2203,7 +2203,6 @@
boolean isIms = phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS;
boolean isVoWifiEnabled = false;
if (isIms) {
- ImsPhone imsPhone = (ImsPhone) phone;
isVoWifiEnabled = ImsUtil.isWfcEnabled(phone.getContext(), phone.getPhoneId());
}
PhoneAccountHandle phoneAccountHandle = isIms ? PhoneUtils
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
diff --git a/tests/src/com/android/services/telephony/ImsConferenceTest.java b/tests/src/com/android/services/telephony/ImsConferenceTest.java
index 56a6240..909ab65 100644
--- a/tests/src/com/android/services/telephony/ImsConferenceTest.java
+++ b/tests/src/com/android/services/telephony/ImsConferenceTest.java
@@ -32,6 +32,7 @@
import android.os.Looper;
import android.telecom.ConferenceParticipant;
import android.telecom.Connection;
+import android.telecom.PhoneAccountHandle;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.internal.telephony.PhoneConstants;
@@ -53,9 +54,6 @@
@Mock
private TelecomAccountRegistry mMockTelecomAccountRegistry;
- @Mock
- private com.android.internal.telephony.Connection mOriginalConnection;
-
private TestTelephonyConnection mConferenceHost;
@Before
@@ -66,11 +64,16 @@
}
mConferenceHost = new TestTelephonyConnection();
mConferenceHost.setManageImsConferenceCallSupported(true);
+ when(mMockTelecomAccountRegistry.getAddress(any(PhoneAccountHandle.class)))
+ .thenReturn(null);
}
@Test
@SmallTest
public void testSinglePartyEmulation() {
+ when(mMockTelecomAccountRegistry.isUsingSimCallManager(any(PhoneAccountHandle.class)))
+ .thenReturn(false);
+
ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
mMockTelephonyConnectionServiceProxy, mConferenceHost,
null /* phoneAccountHandle */, () -> true /* featureFlagProxy */);
@@ -100,9 +103,51 @@
assertEquals(2, imsConference.getNumberOfParticipants());
}
+ /**
+ * Verify that we do not use single party emulation when a sim call manager is in use.
+ */
+ @Test
+ @SmallTest
+ public void testNoSinglePartyEmulationWithSimCallManager() {
+ // Make it look like there is a sim call manager in use.
+ when(mMockTelecomAccountRegistry.isUsingSimCallManager(
+ any(PhoneAccountHandle.class))).thenReturn(true);
+
+ ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
+ mMockTelephonyConnectionServiceProxy, mConferenceHost,
+ null /* phoneAccountHandle */, () -> true /* featureFlagProxy */);
+
+ ConferenceParticipant participant1 = new ConferenceParticipant(
+ Uri.parse("tel:6505551212"),
+ "A",
+ Uri.parse("sip:6505551212@testims.com"),
+ Connection.STATE_ACTIVE);
+ ConferenceParticipant participant2 = new ConferenceParticipant(
+ Uri.parse("tel:6505551213"),
+ "A",
+ Uri.parse("sip:6505551213@testims.com"),
+ Connection.STATE_ACTIVE);
+ imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+ Arrays.asList(participant1, participant2));
+ assertEquals(2, imsConference.getNumberOfParticipants());
+
+ // Because we're not using single party emulation, should still be one participant.
+ imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+ Arrays.asList(participant1));
+ assertEquals(1, imsConference.getNumberOfParticipants());
+
+ // Back to 2!
+ imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+ Arrays.asList(participant1, participant2));
+ assertEquals(2, imsConference.getNumberOfParticipants());
+ }
+
@Test
@SmallTest
public void testNormalConference() {
+ when(mMockTelecomAccountRegistry.isUsingSimCallManager(any(PhoneAccountHandle.class)))
+ .thenReturn(false);
+
ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
mMockTelephonyConnectionServiceProxy, mConferenceHost,
null /* phoneAccountHandle */, () -> false /* featureFlagProxy */);
diff --git a/tests/src/com/android/services/telephony/TestTelephonyConnection.java b/tests/src/com/android/services/telephony/TestTelephonyConnection.java
index c064ef6..b6e4bf3 100644
--- a/tests/src/com/android/services/telephony/TestTelephonyConnection.java
+++ b/tests/src/com/android/services/telephony/TestTelephonyConnection.java
@@ -82,9 +82,11 @@
any(Connection.PostDialListener.class));
when(mMockPhone.getRingingCall()).thenReturn(mMockCall);
when(mMockPhone.getContext()).thenReturn(mMockContext);
+ when(mMockPhone.getCurrentSubscriberUris()).thenReturn(null);
when(mMockContext.getResources()).thenReturn(mMockResources);
when(mMockResources.getBoolean(anyInt())).thenReturn(false);
when(mMockPhone.getDefaultPhone()).thenReturn(mMockPhone);
+ when(mMockPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_IMS);
when(mMockCall.getState()).thenReturn(Call.State.ACTIVE);
when(mMockCall.getPhone()).thenReturn(mMockPhone);
}