Merge "Add CrossSimRedialingController" into udc-dev
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 71b2a9a..57f85ca 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -870,7 +870,7 @@
<string name="radioInfo_lac" msgid="3892986460272607013">"LAC"</string>
<string name="radioInfo_cid" msgid="1423185536264406705">"CID"</string>
<string name="radio_info_subid" msgid="6839966868621703203">"Sous-identifiant actuel :"</string>
- <string name="radio_info_dds" msgid="1122593144425697126">"Sous-identifiant de la carte SIM par défaut pour les données :"</string>
+ <string name="radio_info_dds" msgid="1122593144425697126">"Sous-identifiant SIM par défaut pour les données :"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Bande passante de téléchargement (kbit/s) :"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Bande passante d\'importation (kbit/s) :"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configuration de la chaîne physique LTE :"</string>
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index b49ff4e..8c668e3 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -51,7 +51,6 @@
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
-import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IBinder;
import android.os.ICancellationSignal;
@@ -150,7 +149,6 @@
import android.telephony.satellite.ISatellitePositionUpdateCallback;
import android.telephony.satellite.ISatelliteProvisionStateCallback;
import android.telephony.satellite.ISatelliteStateCallback;
-import android.telephony.satellite.PointingInfo;
import android.telephony.satellite.SatelliteCapabilities;
import android.telephony.satellite.SatelliteDatagram;
import android.telephony.satellite.SatelliteDatagramCallback;
@@ -198,7 +196,6 @@
import com.android.internal.telephony.ProxyController;
import com.android.internal.telephony.RIL;
import com.android.internal.telephony.RILConstants;
-import com.android.internal.telephony.RILUtils;
import com.android.internal.telephony.RadioInterfaceCapabilityController;
import com.android.internal.telephony.ServiceStateTracker;
import com.android.internal.telephony.SmsApplication;
@@ -266,7 +263,6 @@
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
@@ -6764,7 +6760,7 @@
mApp, subId, "getAllowedNetworkTypesForReason");
final long identity = Binder.clearCallingIdentity();
try {
- return getPhoneFromSubId(subId).getAllowedNetworkTypes(reason);
+ return getPhoneFromSubIdOrDefault(subId).getAllowedNetworkTypes(reason);
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/src/com/android/phone/PhoneUtils.java b/src/com/android/phone/PhoneUtils.java
index 5f14387..4826d2b 100644
--- a/src/com/android/phone/PhoneUtils.java
+++ b/src/com/android/phone/PhoneUtils.java
@@ -713,11 +713,11 @@
// an emergency-only account
String id = isEmergency ? EMERGENCY_ACCOUNT_HANDLE_ID : prefix +
String.valueOf(phone.getSubId());
- return makePstnPhoneAccountHandleWithPrefix(id, prefix, isEmergency, userHandle);
+ return makePstnPhoneAccountHandleWithId(id, userHandle);
}
- public static PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix(
- String id, String prefix, boolean isEmergency, UserHandle userHandle) {
+ public static PhoneAccountHandle makePstnPhoneAccountHandleWithId(
+ String id, UserHandle userHandle) {
ComponentName pstnConnectionServiceName = getPstnConnectionServiceName();
// If user handle is null, resort to default constructor to use phone process's
// user handle
diff --git a/src/com/android/phone/TimeConsumingPreferenceActivity.java b/src/com/android/phone/TimeConsumingPreferenceActivity.java
index d21f6a8..1fe548c 100644
--- a/src/com/android/phone/TimeConsumingPreferenceActivity.java
+++ b/src/com/android/phone/TimeConsumingPreferenceActivity.java
@@ -192,7 +192,12 @@
if (mIsForeground) {
showDialog(error);
}
- preference.setEnabled(false);
+
+ //If the error is due to RESPONSE_ERROR, do not disable the item so end user
+ //can continue to interact with it.
+ if (error != RESPONSE_ERROR) {
+ preference.setEnabled(false);
+ }
}
@Override
diff --git a/src/com/android/phone/slice/PremiumNetworkEntitlementApi.java b/src/com/android/phone/slice/PremiumNetworkEntitlementApi.java
index 2546023..8288c43 100644
--- a/src/com/android/phone/slice/PremiumNetworkEntitlementApi.java
+++ b/src/com/android/phone/slice/PremiumNetworkEntitlementApi.java
@@ -45,7 +45,7 @@
private static final String ENTITLEMENT_STATUS_KEY = "EntitlementStatus";
private static final String PROVISION_STATUS_KEY = "ProvStatus";
private static final String SERVICE_FLOW_URL_KEY = "ServiceFlow_URL";
- private static final String PROVISION_TIME_LEFT_KEY = "ProvisionTimeLeft";
+ private static final String SERVICE_FLOW_USERDATA_KEY = "ServiceFlow_UserData";
private static final String DEFAULT_EAP_AKA_RESPONSE = "Default EAP AKA response";
/**
* UUID to report an anomaly if an unexpected error is received during entitlement check.
@@ -100,8 +100,7 @@
requestBuilder.setTerminalModel("modelY");
requestBuilder.setTerminalSoftwareVersion("versionZ");
requestBuilder.setAcceptContentType(ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_JSON);
- requestBuilder.setNetworkIdentifier(
- TelephonyManager.convertPremiumCapabilityToString(capability));
+ requestBuilder.setBoostType(getBoostTypeFromPremiumCapability(capability));
ServiceEntitlementRequest request = requestBuilder.build();
PremiumNetworkEntitlementResponse premiumNetworkEntitlementResponse =
new PremiumNetworkEntitlementResponse();
@@ -109,7 +108,7 @@
String response = null;
try {
response = mServiceEntitlement.queryEntitlementStatus(
- ServiceEntitlement.APP_PREMIUM_NETWORK_SLICE,
+ ServiceEntitlement.APP_DATA_PLAN_BOOST,
request);
} catch (ServiceEntitlementException e) {
Log.e(TAG, "queryEntitlementStatus failed", e);
@@ -123,10 +122,9 @@
JSONObject jsonAuthResponse = new JSONObject(response);
String entitlementStatus = null;
String provisionStatus = null;
- String provisionTimeLeft = null;
- if (jsonAuthResponse.has(ServiceEntitlement.APP_PREMIUM_NETWORK_SLICE)) {
+ if (jsonAuthResponse.has(ServiceEntitlement.APP_DATA_PLAN_BOOST)) {
JSONObject jsonToken = jsonAuthResponse.getJSONObject(
- ServiceEntitlement.APP_PREMIUM_NETWORK_SLICE);
+ ServiceEntitlement.APP_DATA_PLAN_BOOST);
if (jsonToken.has(ENTITLEMENT_STATUS_KEY)) {
entitlementStatus = jsonToken.getString(ENTITLEMENT_STATUS_KEY);
if (entitlementStatus == null) {
@@ -142,20 +140,17 @@
Integer.parseInt(provisionStatus);
}
}
- if (jsonToken.has(PROVISION_TIME_LEFT_KEY)) {
- provisionTimeLeft = jsonToken.getString(PROVISION_TIME_LEFT_KEY);
- if (provisionTimeLeft != null) {
- premiumNetworkEntitlementResponse.mProvisionTimeLeft =
- Integer.parseInt(provisionTimeLeft);
- }
- }
if (jsonToken.has(SERVICE_FLOW_URL_KEY)) {
premiumNetworkEntitlementResponse.mServiceFlowURL =
jsonToken.getString(SERVICE_FLOW_URL_KEY);
}
+ if (jsonToken.has(SERVICE_FLOW_USERDATA_KEY)) {
+ premiumNetworkEntitlementResponse.mServiceFlowUserData =
+ jsonToken.getString(SERVICE_FLOW_USERDATA_KEY);
+ }
+ } else {
+ Log.e(TAG, "queryEntitlementStatus failed with no app");
}
-
-
} catch (JSONException e) {
Log.e(TAG, "queryEntitlementStatus failed", e);
reportAnomaly(UUID_ENTITLEMENT_CHECK_UNEXPECTED_ERROR,
@@ -194,4 +189,12 @@
return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TELEPHONY,
BYPASS_EAP_AKA_AUTH_FOR_SLICE_PURCHASE_ENABLED, false);
}
+
+ @NonNull private String getBoostTypeFromPremiumCapability(
+ @TelephonyManager.PremiumCapability int capability) {
+ if (capability == TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY) {
+ return "0" /* REALTIME_INTERACTIVE_TRAFFIC */;
+ }
+ return "";
+ }
}
diff --git a/src/com/android/phone/slice/PremiumNetworkEntitlementResponse.java b/src/com/android/phone/slice/PremiumNetworkEntitlementResponse.java
index 4e63e35..242ca69 100644
--- a/src/com/android/phone/slice/PremiumNetworkEntitlementResponse.java
+++ b/src/com/android/phone/slice/PremiumNetworkEntitlementResponse.java
@@ -30,40 +30,39 @@
public static final int PREMIUM_NETWORK_ENTITLEMENT_STATUS_PROVISIONING = 3;
public static final int PREMIUM_NETWORK_ENTITLEMENT_STATUS_INCLUDED = 4;
- @IntDef(prefix = {"PREMIUM_NETWORK_ENTITLEMENT_STATUS_"},
- value = {
- PREMIUM_NETWORK_ENTITLEMENT_STATUS_DISABLED,
- PREMIUM_NETWORK_ENTITLEMENT_STATUS_ENABLED,
- PREMIUM_NETWORK_ENTITLEMENT_STATUS_INCOMPATIBLE,
- PREMIUM_NETWORK_ENTITLEMENT_STATUS_PROVISIONING,
- PREMIUM_NETWORK_ENTITLEMENT_STATUS_INCLUDED
- })
+ @IntDef(prefix = {"PREMIUM_NETWORK_ENTITLEMENT_STATUS_"}, value = {
+ PREMIUM_NETWORK_ENTITLEMENT_STATUS_DISABLED,
+ PREMIUM_NETWORK_ENTITLEMENT_STATUS_ENABLED,
+ PREMIUM_NETWORK_ENTITLEMENT_STATUS_INCOMPATIBLE,
+ PREMIUM_NETWORK_ENTITLEMENT_STATUS_PROVISIONING,
+ PREMIUM_NETWORK_ENTITLEMENT_STATUS_INCLUDED
+ })
public @interface PremiumNetworkEntitlementStatus {}
public static final int PREMIUM_NETWORK_PROVISION_STATUS_NOT_PROVISIONED = 0;
public static final int PREMIUM_NETWORK_PROVISION_STATUS_PROVISIONED = 1;
- public static final int PREMIUM_NETWORK_PROVISION_STATUS_NOT_REQUIRED = 2;
+ public static final int PREMIUM_NETWORK_PROVISION_STATUS_NOT_AVAILABLE = 2;
public static final int PREMIUM_NETWORK_PROVISION_STATUS_IN_PROGRESS = 3;
- @IntDef(prefix = {"PREMIUM_NETWORK_PROVISION_STATUS_"},
- value = {
- PREMIUM_NETWORK_PROVISION_STATUS_NOT_PROVISIONED,
- PREMIUM_NETWORK_PROVISION_STATUS_PROVISIONED,
- PREMIUM_NETWORK_PROVISION_STATUS_NOT_REQUIRED,
- PREMIUM_NETWORK_PROVISION_STATUS_IN_PROGRESS
- })
+ @IntDef(prefix = {"PREMIUM_NETWORK_PROVISION_STATUS_"}, value = {
+ PREMIUM_NETWORK_PROVISION_STATUS_NOT_PROVISIONED,
+ PREMIUM_NETWORK_PROVISION_STATUS_PROVISIONED,
+ PREMIUM_NETWORK_PROVISION_STATUS_NOT_AVAILABLE,
+ PREMIUM_NETWORK_PROVISION_STATUS_IN_PROGRESS
+ })
public @interface PremiumNetworkProvisionStatus {}
@PremiumNetworkEntitlementStatus public int mEntitlementStatus;
@PremiumNetworkProvisionStatus public int mProvisionStatus;
- public int mProvisionTimeLeft;
@NonNull public String mServiceFlowURL;
+ @NonNull public String mServiceFlowUserData;
/**
* @return {@code true} if the premium network is provisioned and {@code false} otherwise.
*/
public boolean isProvisioned() {
return mProvisionStatus == PREMIUM_NETWORK_PROVISION_STATUS_PROVISIONED
+ || mEntitlementStatus == PREMIUM_NETWORK_ENTITLEMENT_STATUS_ENABLED
|| mEntitlementStatus == PREMIUM_NETWORK_ENTITLEMENT_STATUS_INCLUDED;
}
@@ -83,7 +82,10 @@
public boolean isPremiumNetworkCapabilityAllowed() {
switch (mEntitlementStatus) {
case PREMIUM_NETWORK_ENTITLEMENT_STATUS_INCOMPATIBLE:
- case PREMIUM_NETWORK_ENTITLEMENT_STATUS_DISABLED:
+ return false;
+ }
+ switch (mProvisionStatus) {
+ case PREMIUM_NETWORK_PROVISION_STATUS_NOT_AVAILABLE:
return false;
}
return true;
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index c62b4fa..654fb93 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -980,23 +980,25 @@
// event package; some carriers are known to keep a disconnected participant around in
// subsequent CEP updates with a state of disconnected, even though its no longer part
// of the conference.
- // Note: We consider 0 to still be a single party conference since some carriers will
- // send a conference event package with JUST the host in it when the conference is
- // disconnected. We don't want to change back to conference mode prior to disconnection
- // or we will not log the call.
- boolean isSinglePartyConference = participants.stream()
+ final long numActiveCepParticipantsOtherThanHost = participants.stream()
.filter(p -> {
Pair<Uri, Uri> pIdent = new Pair<>(p.getHandle(), p.getEndpoint());
return !Objects.equals(mHostParticipantIdentity, pIdent)
&& p.getState() != Connection.STATE_DISCONNECTED;
})
- .count() <= 1;
+ .count();
+ // We consider 0 to still be a single party conference since some carriers
+ // will send a conference event package with JUST the host in it when the conference
+ // is disconnected. We don't want to change back to conference mode prior to
+ // disconnection or we will not log the call.
+ final boolean isCepForSinglePartyConference =
+ numActiveCepParticipantsOtherThanHost <= 1;
// We will only process the CEP data if:
// 1. We're not emulating a single party call.
// 2. We're emulating a single party call and the CEP contains more than just the
// single party
- if ((!isMultiparty() && !isSinglePartyConference)
+ if ((!isMultiparty() && !isCepForSinglePartyConference)
|| isMultiparty()) {
// Add any new participants and update existing.
for (ConferenceParticipant participant : participants) {
@@ -1082,15 +1084,17 @@
int newParticipantCount = mConferenceParticipantConnections.size();
Log.v(this, "handleConferenceParticipantsUpdate: oldParticipantCount=%d, "
- + "newParticipantcount=%d", oldParticipantCount, newParticipantCount);
- // If the single party call emulation fature flag is enabled, we can potentially treat
+ + "newParticipantCount=%d, isMultiPty=%b, cepParticipantCt=%d",
+ oldParticipantCount, newParticipantCount, isMultiparty(),
+ numActiveCepParticipantsOtherThanHost);
+ // If the single party call emulation feature flag is enabled, we can potentially treat
// the conference as a single party call when there is just one participant.
if (mFeatureFlagProxy.isUsingSinglePartyCallEmulation() &&
!mConferenceHost.isAdhocConferenceCall()) {
if (oldParticipantCount != 1 && newParticipantCount == 1) {
// If number of participants goes to 1, emulate a single party call.
startEmulatingSinglePartyCall();
- } else if (!isMultiparty() && !isSinglePartyConference) {
+ } else if (!isMultiparty() && !isCepForSinglePartyConference) {
// Number of participants increased, so stop emulating a single party call.
stopEmulatingSinglePartyCall();
}
@@ -1108,8 +1112,8 @@
// OR if the conference had a single participant and is emulating a standalone
// call.
&& (oldParticipantCount > 0 || !isMultiparty())
- // AND the CEP says there is nobody left any more.
- && newParticipantCount == 0) {
+ // AND the CEP says there is nobody left anymore.
+ && numActiveCepParticipantsOtherThanHost == 0) {
Log.i(this, "handleConferenceParticipantsUpdate: empty conference; "
+ "local disconnect.");
onDisconnect();
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index 6650eac..57e65ee 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -1593,9 +1593,8 @@
int subscriptionId = phone.getSubId();
Log.i(this, "setupAccounts: Phone with subscription id %d", subscriptionId);
// setupAccounts can be called multiple times during service changes.
- // Don't add an account if the Icc has not been set yet.
- if (!SubscriptionManager.isValidSubscriptionId(subscriptionId)
- || phone.getFullIccSerialNumber() == null) {
+ // Don't add an account if subscription is not ready.
+ if (!SubscriptionManager.isValidSubscriptionId(subscriptionId)) {
Log.d(this, "setupAccounts: skipping invalid subid %d", subscriptionId);
continue;
}
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index f381f11..9f248b7 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -16,6 +16,10 @@
package com.android.services.telephony;
+import static android.telephony.ims.ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED;
+import static android.telephony.ims.ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL;
+import static android.telephony.ims.ImsReasonInfo.EXTRA_CODE_CALL_RETRY_EMERGENCY;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ContentResolver;
@@ -2483,13 +2487,25 @@
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 (reasonInfo != null) {
+ int reasonCode = reasonInfo.getCode();
+ int extraCode = reasonInfo.getExtraCode();
+ if ((reasonCode == CODE_SIP_ALTERNATE_EMERGENCY_CALL)
+ || (reasonCode == CODE_LOCAL_CALL_CS_RETRY_REQUIRED
+ && extraCode == EXTRA_CODE_CALL_RETRY_EMERGENCY)) {
+ EmergencyNumber numberInfo =
+ imsPhoneConnection.getEmergencyNumberInfo();
+ if (numberInfo != null) {
+ mEmergencyServiceCategory =
+ numberInfo.getEmergencyServiceCategoryBitmask();
+ } else {
+ Log.i(this, "mEmergencyServiceCategory no EmergencyNumber");
+ }
+
+ if (mEmergencyServiceCategory != null) {
+ Log.i(this, "mEmergencyServiceCategory="
+ + mEmergencyServiceCategory);
+ }
}
}
}
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index e552424..7311113 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -83,6 +83,7 @@
import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.imsphone.ImsPhoneConnection;
import com.android.internal.telephony.imsphone.ImsPhoneMmiCode;
+import com.android.internal.telephony.satellite.SatelliteSOSMessageRecommender;
import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
import com.android.internal.telephony.subscription.SubscriptionManagerService;
import com.android.phone.FrameworksUtils;
@@ -119,6 +120,10 @@
// 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;
+
+ // Timeout before we terminate the outgoing DSDA call if HOLD did not complete in time on the
+ // existing call.
+ private static final int DEFAULT_DSDA_OUTGOING_CALL_HOLD_TIMEOUT_MS = 1000;
private static final String KEY_DOMAIN_COMPARE_FEATURE_ENABLED_FLAG =
"is_domain_selection_compare_feature_enabled";
@@ -205,6 +210,7 @@
public Pair<WeakReference<TelephonyConnection>, Queue<Phone>> mEmergencyRetryCache;
private DeviceState mDeviceState = new DeviceState();
private EmergencyStateTracker mEmergencyStateTracker;
+ private SatelliteSOSMessageRecommender mSatelliteSOSMessageRecommender;
private DomainSelectionResolver mDomainSelectionResolver;
private EmergencyCallDomainSelectionConnection mEmergencyCallDomainSelectionConnection;
private TelephonyConnection mEmergencyConnection;
@@ -568,6 +574,34 @@
}
};
+ private final TelephonyConnection.TelephonyConnectionListener
+ mEmergencyConnectionSatelliteListener =
+ new TelephonyConnection.TelephonyConnectionListener() {
+ @Override
+ public void onStateChanged(Connection connection,
+ @Connection.ConnectionState int state) {
+ if (connection == null) {
+ Log.d(this,
+ "onStateChanged for satellite listener: connection is null");
+ return;
+ }
+ if (mSatelliteSOSMessageRecommender == null) {
+ Log.d(this, "onStateChanged for satellite listener: "
+ + "mSatelliteSOSMessageRecommender is null");
+ return;
+ }
+
+ TelephonyConnection c = (TelephonyConnection) connection;
+ mSatelliteSOSMessageRecommender.onEmergencyCallConnectionStateChanged(
+ c.getTelecomCallId(), state);
+ if (state == Connection.STATE_DISCONNECTED
+ || state == Connection.STATE_ACTIVE) {
+ c.removeTelephonyConnectionListener(mEmergencyConnectionSatelliteListener);
+ mSatelliteSOSMessageRecommender = null;
+ }
+ }
+ };
+
/**
* A listener for calls.
*/
@@ -599,6 +633,39 @@
}
};
+ private static class StateHoldingListener extends
+ TelephonyConnection.TelephonyConnectionListener {
+ private final CompletableFuture<Boolean> mStateHoldingFuture;
+
+ StateHoldingListener(CompletableFuture<Boolean> future) {
+ mStateHoldingFuture = future;
+ }
+
+ @Override
+ public void onStateChanged(
+ Connection connection, @Connection.ConnectionState int state) {
+ TelephonyConnection c = (TelephonyConnection) connection;
+ if (c != null) {
+ switch (c.getState()) {
+ case Connection.STATE_HOLDING: {
+ Log.d(LOG_TAG, "Connection " + connection
+ + " changed to STATE_HOLDING!");
+ mStateHoldingFuture.complete(true);
+ c.removeTelephonyConnectionListener(this);
+ }
+ break;
+ case Connection.STATE_DISCONNECTED: {
+ Log.d(LOG_TAG, "Connection " + connection
+ + " changed to STATE_DISCONNECTED!");
+ mStateHoldingFuture.complete(false);
+ c.removeTelephonyConnectionListener(this);
+ }
+ break;
+ }
+ }
+ }
+ }
+
private final DomainSelectionConnection.DomainSelectionConnectionCallback
mEmergencyDomainSelectionConnectionCallback =
new DomainSelectionConnection.DomainSelectionConnectionCallback() {
@@ -1083,6 +1150,21 @@
}
return resultConnection;
} else {
+ if (mTelephonyManagerProxy.isConcurrentCallsPossible()) {
+ delayDialForOtherSubHold(phone, request.getAccountHandle(), (result) -> {
+ Log.d(this,
+ "onCreateOutgoingConn - delayDialForOtherSubHold result = "
+ + result);
+ if (result) {
+ placeOutgoingConnection(request, resultConnection, phone);
+ } else {
+ ((TelephonyConnection) resultConnection).hangup(
+ android.telephony.DisconnectCause.LOCAL);
+ }
+ });
+ return resultConnection;
+ }
+ // The standard case.
return placeOutgoingConnection(request, resultConnection, phone);
}
} else {
@@ -1959,12 +2041,14 @@
}
});
}
+
final com.android.internal.telephony.Connection originalConnection;
try {
if (phone != null) {
boolean isEmergency = mTelephonyManagerProxy.isCurrentEmergencyNumber(number);
Log.i(this, "placeOutgoingConnection isEmergency=" + isEmergency);
if (isEmergency) {
+ handleEmergencyCallStartedForSatelliteSOSMessageRecommender(connection, phone);
if (!getAllConnections().isEmpty()) {
if (!shouldHoldForEmergencyCall(phone)) {
// If we do not support holding ongoing calls for an outgoing
@@ -2353,10 +2437,23 @@
return false;
}
- if (reasonInfo != null
- && reasonInfo.getCode() == ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL) {
- onEmergencyRedial(c, c.getPhone().getDefaultPhone());
- return true;
+ if (reasonInfo != null) {
+ int reasonCode = reasonInfo.getCode();
+ int extraCode = reasonInfo.getExtraCode();
+ if ((reasonCode == ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL)
+ || (reasonCode == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED
+ && extraCode == ImsReasonInfo.EXTRA_CODE_CALL_RETRY_EMERGENCY)) {
+ // clear normal call domain selector
+ c.removeTelephonyConnectionListener(mNormalCallConnectionListener);
+ if (mDomainSelectionConnection != null) {
+ mDomainSelectionConnection.finishSelection();
+ mDomainSelectionConnection = null;
+ }
+ mNormalCallConnection = null;
+
+ onEmergencyRedial(c, c.getPhone().getDefaultPhone());
+ return true;
+ }
}
return maybeReselectDomainForNormalCall(c, callFailCause, reasonInfo);
@@ -2507,6 +2604,7 @@
mIsEmergencyCallPending = true;
c.addTelephonyConnectionListener(mEmergencyConnectionListener);
+ handleEmergencyCallStartedForSatelliteSOSMessageRecommender(c, phone);
if (mEmergencyStateTracker == null) {
mEmergencyStateTracker = EmergencyStateTracker.getInstance();
@@ -2677,6 +2775,12 @@
return mEmergencyConnectionListener;
}
+ @VisibleForTesting
+ public TelephonyConnection.TelephonyConnectionListener
+ getEmergencyConnectionSatelliteListener() {
+ return mEmergencyConnectionSatelliteListener;
+ }
+
private boolean isVideoCallHoldAllowed(Phone phone) {
CarrierConfigManager cfgManager = (CarrierConfigManager)
phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
@@ -2843,6 +2947,7 @@
if (phone == null) {
// Do not block indefinitely.
completeConsumer.accept(false);
+ return;
}
try {
// Waiting for PhoneSwitcher to complete the operation.
@@ -2962,6 +3067,64 @@
return modemResultFuture;
}
+ private void addTelephonyConnectionListener(Conferenceable c,
+ TelephonyConnection.TelephonyConnectionListener listener) {
+ if (c instanceof TelephonyConnection) {
+ TelephonyConnection telephonyConnection = (TelephonyConnection) c;
+ telephonyConnection.addTelephonyConnectionListener(listener);
+ } else if (c instanceof ImsConference) {
+ ImsConference imsConference = (ImsConference) c;
+ TelephonyConnection conferenceHost =
+ (TelephonyConnection) imsConference.getConferenceHost();
+ conferenceHost.addTelephonyConnectionListener(listener);
+ } else {
+ throw new IllegalArgumentException(
+ "addTelephonyConnectionListener(): Unexpected conferenceable! " + c);
+ }
+ }
+
+ private CompletableFuture<Boolean> listenForHoldStateChanged(
+ @NonNull Conferenceable conferenceable) {
+ CompletableFuture<Boolean> future = new CompletableFuture<>();
+ final StateHoldingListener stateHoldingListener = new StateHoldingListener(future);
+ addTelephonyConnectionListener(conferenceable, stateHoldingListener);
+ return future;
+ }
+
+ // If there are any live calls on the other subscription, sends a hold request for the live call
+ // and waits for the STATE_HOLDING confirmation, to sequence the dial of the outgoing call.
+ private void delayDialForOtherSubHold(Phone phone, PhoneAccountHandle phoneAccountHandle,
+ Consumer<Boolean> completeConsumer) {
+ Conferenceable c = maybeHoldCallsOnOtherSubs(phoneAccountHandle);
+ if (c == null) {
+ // Nothing to hold.
+ completeConsumer.accept(true);
+ return;
+ }
+
+ if (phone == null) {
+ completeConsumer.accept(false);
+ return;
+ }
+
+ try {
+ // We have dispatched a 'hold' command to a live call (Connection or Conference) on the
+ // other sub. Listen to state changed events to see if this entered hold state.
+ CompletableFuture<Boolean> stateHoldingFuture = listenForHoldStateChanged(c);
+ // a timeout that will complete the future to not block the outgoing call indefinitely.
+ CompletableFuture<Boolean> timeout = new CompletableFuture<>();
+ phone.getContext().getMainThreadHandler().postDelayed(
+ () -> timeout.complete(false), DEFAULT_DSDA_OUTGOING_CALL_HOLD_TIMEOUT_MS);
+ // Ensure that the Consumer is completed on the main thread.
+ stateHoldingFuture.acceptEitherAsync(timeout, completeConsumer,
+ phone.getContext().getMainExecutor());
+ } catch (Exception e) {
+ Log.w(this, "delayDialForOtherSubHold - exception= "
+ + e.getMessage());
+ completeConsumer.accept(false);
+ }
+ }
+
/**
* 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
@@ -3579,7 +3742,20 @@
});
}
- private static void onUnhold(Conferenceable conferenceable) {
+ static void onHold(Conferenceable conferenceable) {
+ if (conferenceable instanceof Connection) {
+ Connection connection = (Connection) conferenceable;
+ connection.onHold();
+ } else if (conferenceable instanceof Conference) {
+ Conference conference = (Conference) conferenceable;
+ conference.onHold();
+ } else {
+ throw new IllegalArgumentException(
+ "onHold(): Unexpected conferenceable! " + conferenceable);
+ }
+ }
+
+ static void onUnhold(Conferenceable conferenceable) {
if (conferenceable instanceof Connection) {
Connection connection = (Connection) conferenceable;
connection.onUnhold();
@@ -3587,40 +3763,28 @@
Conference conference = (Conference) conferenceable;
conference.onUnhold();
} else {
- throw new IllegalArgumentException("Unexpected conferenceable! " + conferenceable);
+ throw new IllegalArgumentException(
+ "onUnhold(): Unexpected conferenceable! " + conferenceable);
}
}
- /**
- * Where there are ongoing calls on multiple subscriptions for DSDA devices, let the 'hold'
- * button perform an unhold on the other sub's Connection or Conference. This covers for Dialer
- * apps that may not have a dedicated 'swap' button for calls across different subs.
- * @param incomingHandle The incoming {@link PhoneAccountHandle}.
- */
- public void maybeUnholdCallsOnOtherSubs(@NonNull PhoneAccountHandle incomingHandle) {
- Log.i(this, "maybeUnholdCallsOnOtherSubs: check for calls not on %s",
- incomingHandle);
- maybeUnholdCallsOnOtherSubs(getAllConnections(), getAllConferences(), incomingHandle,
- mTelephonyManagerProxy);
- }
-
- /**
- * Used by {@link #maybeUnholdCallsOnOtherSubs(PhoneAccountHandle)} to evaluate whether and on
- * which connection / conference to call onUnhold(). This method exists as a convenience so that
- * it is possible to unit test the core functionality.
+ /**
+ * Evaluates whether a connection or conference exists on subscriptions other than the one
+ * corresponding to the existing {@link PhoneAccountHandle}.
* @param connections all individual connections, including conference participants.
* @param conferences all conferences.
- * @param incomingHandle the incoming handle.
+ * @param currentHandle the existing call handle;
* @param telephonyManagerProxy the proxy to the {@link TelephonyManager} instance.
*/
- @VisibleForTesting
- public static void maybeUnholdCallsOnOtherSubs(@NonNull Collection<Connection> connections,
+ private static @Nullable Conferenceable maybeGetFirstConferenceableFromOtherSubscription(
+ @NonNull Collection<Connection> connections,
@NonNull Collection<Conference> conferences,
- @NonNull PhoneAccountHandle incomingHandle,
+ @NonNull PhoneAccountHandle currentHandle,
TelephonyManagerProxy telephonyManagerProxy) {
if (!telephonyManagerProxy.isConcurrentCallsPossible()) {
- return;
+ return null;
}
+
List<Conference> otherSubConferences = conferences.stream()
.filter(c ->
// Exclude multiendpoint calls as they're not on this device.
@@ -3628,11 +3792,10 @@
& Connection.PROPERTY_IS_EXTERNAL_CALL) == 0
// Include any conferences not on same sub as current connection.
&& !Objects.equals(c.getPhoneAccountHandle(),
- incomingHandle))
+ currentHandle))
.toList();
if (!otherSubConferences.isEmpty()) {
- onUnhold(otherSubConferences.get(0));
- return;
+ return otherSubConferences.get(0);
}
// Considers Connections (including conference participants) only if no conferences.
@@ -3642,15 +3805,91 @@
(c.getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL) == 0
// Include any calls not on same sub as current connection.
&& !Objects.equals(c.getPhoneAccountHandle(),
- incomingHandle)).toList();
+ currentHandle)).toList();
if (!otherSubConnections.isEmpty()) {
if (otherSubConnections.size() > 1) {
- Log.w(LOG_TAG, "Unexpected number of conferenceables: "
+ Log.w(LOG_TAG, "Unexpected number of connections: "
+ otherSubConnections.size() + " on other sub!");
}
- onUnhold(otherSubConnections.get(0));
+ return otherSubConnections.get(0);
}
+ return null;
+ }
+
+ /**
+ * Where there are ongoing calls on multiple subscriptions for DSDA devices, let the 'hold'
+ * button perform an unhold on the other sub's Connection or Conference. This covers for Dialer
+ * apps that may not have a dedicated 'swap' button for calls across different subs.
+ * @param currentHandle The {@link PhoneAccountHandle} of the current active voice call.
+ */
+ public void maybeUnholdCallsOnOtherSubs(
+ @NonNull PhoneAccountHandle currentHandle) {
+ Log.i(this, "maybeUnholdCallsOnOtherSubs: check for calls not on %s",
+ currentHandle);
+ maybeUnholdCallsOnOtherSubs(getAllConnections(), getAllConferences(),
+ currentHandle, mTelephonyManagerProxy);
+ }
+
+ /**
+ * Where there are ongoing calls on multiple subscriptions for DSDA devices, let the 'hold'
+ * button perform an unhold on the other sub's Connection or Conference. This is a convenience
+ * method to unit test the core functionality.
+ *
+ * @param connections all individual connections, including conference participants.
+ * @param conferences all conferences.
+ * @param currentHandle The {@link PhoneAccountHandle} of the current active call.
+ * @param telephonyManagerProxy the proxy to the {@link TelephonyManager} instance.
+ */
+ @VisibleForTesting
+ protected static void maybeUnholdCallsOnOtherSubs(@NonNull Collection<Connection> connections,
+ @NonNull Collection<Conference> conferences,
+ @NonNull PhoneAccountHandle currentHandle,
+ TelephonyManagerProxy telephonyManagerProxy) {
+ Conferenceable c = maybeGetFirstConferenceableFromOtherSubscription(
+ connections, conferences, currentHandle, telephonyManagerProxy);
+ if (c != null) {
+ onUnhold(c);
+ }
+ }
+
+ /**
+ * For DSDA devices, when an outgoing call is dialed out from the 2nd sub, holds the first call.
+ *
+ * @param outgoingHandle The outgoing {@link PhoneAccountHandle}.
+ * @return the Conferenceable representing the Connection or Conference to be held.
+ */
+ private @Nullable Conferenceable maybeHoldCallsOnOtherSubs(
+ @NonNull PhoneAccountHandle outgoingHandle) {
+ Log.i(this, "maybeHoldCallsOnOtherSubs: check for calls not on %s",
+ outgoingHandle);
+ return maybeHoldCallsOnOtherSubs(getAllConnections(), getAllConferences(),
+ outgoingHandle, mTelephonyManagerProxy);
+ }
+
+ /**
+ * For DSDA devices, when an outgoing call is dialed out from the 2nd sub, holds the first call.
+ * This is a convenience method to unit test the core functionality.
+ *
+ * @param connections all individual connections, including conference participants.
+ * @param conferences all conferences.
+ * @param outgoingHandle The outgoing {@link PhoneAccountHandle}.
+ * @param telephonyManagerProxy the proxy to the {@link TelephonyManager} instance.
+ * @return the {@link Conferenceable} representing the Connection or Conference to be held.
+ */
+ @VisibleForTesting
+ protected static @Nullable Conferenceable maybeHoldCallsOnOtherSubs(
+ @NonNull Collection<Connection> connections,
+ @NonNull Collection<Conference> conferences,
+ @NonNull PhoneAccountHandle outgoingHandle,
+ TelephonyManagerProxy telephonyManagerProxy) {
+ Conferenceable c = maybeGetFirstConferenceableFromOtherSubscription(
+ connections, conferences, outgoingHandle, telephonyManagerProxy);
+ if (c != null) {
+ onHold(c);
+ return c;
+ }
+ return null;
}
private void disconnectAllCallsOnOtherSubs (@NonNull PhoneAccountHandle handle) {
@@ -3692,4 +3931,14 @@
}
return NetworkRegistrationInfo.DOMAIN_UNKNOWN;
}
+
+ private void handleEmergencyCallStartedForSatelliteSOSMessageRecommender(
+ @NonNull TelephonyConnection connection, @NonNull Phone phone) {
+ if (mSatelliteSOSMessageRecommender == null) {
+ mSatelliteSOSMessageRecommender = new SatelliteSOSMessageRecommender(
+ phone.getContext().getMainLooper());
+ }
+ connection.addTelephonyConnectionListener(mEmergencyConnectionSatelliteListener);
+ mSatelliteSOSMessageRecommender.onEmergencyCallStarted(connection, phone);
+ }
}
diff --git a/tests/src/com/android/phone/PhoneUtilsTest.java b/tests/src/com/android/phone/PhoneUtilsTest.java
index b5ff0dc..3d7815c 100644
--- a/tests/src/com/android/phone/PhoneUtilsTest.java
+++ b/tests/src/com/android/phone/PhoneUtilsTest.java
@@ -82,8 +82,8 @@
public void testMakePstnPhoneAccountHandleWithPrefix() throws Exception {
PhoneAccountHandle phoneAccountHandleTest = new PhoneAccountHandle(
PSTN_CONNECTION_SERVICE_COMPONENT, mPhoneAccountHandleIdString);
- assertEquals(phoneAccountHandleTest, PhoneUtils.makePstnPhoneAccountHandleWithPrefix(
- mPhoneAccountHandleIdString, "", false, null));
+ assertEquals(phoneAccountHandleTest, PhoneUtils.makePstnPhoneAccountHandleWithId(
+ mPhoneAccountHandleIdString, null));
}
@Test
@@ -91,7 +91,7 @@
UserHandle userHandle = new UserHandle(10);
PhoneAccountHandle phoneAccountHandleTest = new PhoneAccountHandle(
PSTN_CONNECTION_SERVICE_COMPONENT, mPhoneAccountHandleIdString, userHandle);
- assertEquals(phoneAccountHandleTest, PhoneUtils.makePstnPhoneAccountHandleWithPrefix(
- mPhoneAccountHandleIdString, "", false, userHandle));
+ assertEquals(phoneAccountHandleTest, PhoneUtils.makePstnPhoneAccountHandleWithId(
+ mPhoneAccountHandleIdString, userHandle));
}
}
diff --git a/tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java b/tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java
index 921babb..544d7ea 100644
--- a/tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java
+++ b/tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java
@@ -715,7 +715,7 @@
doReturn(TelephonyManager.NETWORK_TYPE_NR).when(mServiceState).getDataNetworkType();
// entitlement check passed
mEntitlementResponse.mEntitlementStatus =
- PremiumNetworkEntitlementResponse.PREMIUM_NETWORK_ENTITLEMENT_STATUS_ENABLED;
+ PremiumNetworkEntitlementResponse.PREMIUM_NETWORK_ENTITLEMENT_STATUS_DISABLED;
// send purchase request
mSlicePurchaseController.purchasePremiumCapability(
diff --git a/tests/src/com/android/services/telephony/ImsConferenceTest.java b/tests/src/com/android/services/telephony/ImsConferenceTest.java
index 9d2f5ac..5123858 100644
--- a/tests/src/com/android/services/telephony/ImsConferenceTest.java
+++ b/tests/src/com/android/services/telephony/ImsConferenceTest.java
@@ -590,7 +590,7 @@
ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
mMockTelephonyConnectionServiceProxy, mConferenceHost,
- null /* phoneAccountHandle */, () -> false /* featureFlagProxy */,
+ null /* phoneAccountHandle */, () -> true /* isUsingSinglePartyCallEmulation */,
new ImsConference.CarrierConfiguration.Builder()
.setShouldLocalDisconnectEmptyConference(true)
.build());
@@ -622,6 +622,109 @@
}
/**
+ * Preconditions: both single party emulation and local disconnect of empty conferences is
+ * enabled.
+ * Tests the case where we receive a repeat with the same single-party data that caused a
+ * conference to be treated as a single party; we need to verify that we do not disconnect the
+ * conference locally in this case.
+ * @throws Exception
+ */
+ @Test
+ @SmallTest
+ public void testNoLocalDisconnectSinglePartyConferenceOnRepeatedCep() throws Exception {
+ when(mMockTelecomAccountRegistry.isUsingSimCallManager(any(PhoneAccountHandle.class)))
+ .thenReturn(false);
+
+ ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
+ mMockTelephonyConnectionServiceProxy, mConferenceHost,
+ null /* phoneAccountHandle */, () -> true /* isUsingSinglePartyCallEmulation */,
+ new ImsConference.CarrierConfiguration.Builder()
+ .setShouldLocalDisconnectEmptyConference(true)
+ .build());
+
+ ConferenceParticipant participant1 = new ConferenceParticipant(
+ Uri.parse("tel:6505551212"),
+ "A",
+ Uri.parse("sip:[email protected]"),
+ Connection.STATE_ACTIVE,
+ Call.Details.DIRECTION_INCOMING);
+ ConferenceParticipant participant2 = new ConferenceParticipant(
+ Uri.parse("tel:6505551213"),
+ "A",
+ Uri.parse("sip:[email protected]"),
+ Connection.STATE_ACTIVE,
+ Call.Details.DIRECTION_INCOMING);
+ imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+ Arrays.asList(participant1, participant2));
+ assertEquals(2, imsConference.getNumberOfParticipants());
+
+ // Drop to 1 participant which enters single party mode.
+ imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+ Arrays.asList(participant1));
+ assertEquals(0, imsConference.getNumberOfParticipants());
+
+ // Get a repeat CEP with the same participant data; we should still be in single party mode
+ // but we should NOT disconnect the conference.
+ imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+ Arrays.asList(participant1));
+ assertEquals(0, imsConference.getNumberOfParticipants());
+ verify(mConferenceHost.mMockCall, never()).hangup();
+ }
+
+ /**
+ * An extension of {@link #testNoLocalDisconnectSinglePartyConferenceOnRepeatedCep()} where we
+ * get a repeated CEP with the same single party state, but then finally get a CEP with no
+ * participants anymore. In this case we do expect a local disconnect as the final state.
+ * @throws Exception
+ */
+ @Test
+ @SmallTest
+ public void testLocalDisconnectSinglePartyConferenceOnRepeatedCep() throws Exception {
+ when(mMockTelecomAccountRegistry.isUsingSimCallManager(any(PhoneAccountHandle.class)))
+ .thenReturn(false);
+
+ ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
+ mMockTelephonyConnectionServiceProxy, mConferenceHost,
+ null /* phoneAccountHandle */, () -> true /* isUsingSinglePartyCallEmulation */,
+ new ImsConference.CarrierConfiguration.Builder()
+ .setShouldLocalDisconnectEmptyConference(true)
+ .build());
+
+ ConferenceParticipant participant1 = new ConferenceParticipant(
+ Uri.parse("tel:6505551212"),
+ "A",
+ Uri.parse("sip:[email protected]"),
+ Connection.STATE_ACTIVE,
+ Call.Details.DIRECTION_INCOMING);
+ ConferenceParticipant participant2 = new ConferenceParticipant(
+ Uri.parse("tel:6505551213"),
+ "A",
+ Uri.parse("sip:[email protected]"),
+ Connection.STATE_ACTIVE,
+ Call.Details.DIRECTION_INCOMING);
+ imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+ Arrays.asList(participant1, participant2));
+ assertEquals(2, imsConference.getNumberOfParticipants());
+
+ // Drop to 1 participant which enters single party mode.
+ imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+ Arrays.asList(participant1));
+ assertEquals(0, imsConference.getNumberOfParticipants());
+
+ // Get a repeat CEP with the same participant data; we should still be in single party mode
+ // but we should NOT disconnect the conference.
+ imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+ Arrays.asList(participant1));
+ assertEquals(0, imsConference.getNumberOfParticipants());
+ verify(mConferenceHost.mMockCall, never()).hangup();
+
+ // Got another CEP that has no participants at all; we should disconnet in this case
+ imsConference.handleConferenceParticipantsUpdate(mConferenceHost, Collections.emptyList());
+ assertEquals(0, imsConference.getNumberOfParticipants());
+ verify(mConferenceHost.mMockCall).hangup();
+ }
+
+ /**
* Tests a scenario where a handover connection arrives via
* {@link TelephonyConnection#onOriginalConnectionRedialed(
* com.android.internal.telephony.Connection)}. During this process, the conference properties
diff --git a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
index a4743c9..f115a43 100644
--- a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
@@ -55,7 +55,9 @@
import android.os.AsyncResult;
import android.os.Bundle;
import android.os.Handler;
+import android.os.Looper;
import android.telecom.Conference;
+import android.telecom.Conferenceable;
import android.telecom.ConnectionRequest;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccountHandle;
@@ -91,6 +93,7 @@
import com.android.internal.telephony.emergency.EmergencyStateTracker;
import com.android.internal.telephony.gsm.SuppServiceNotification;
import com.android.internal.telephony.imsphone.ImsPhone;
+import com.android.internal.telephony.satellite.SatelliteSOSMessageRecommender;
import org.junit.After;
import org.junit.Before;
@@ -122,6 +125,7 @@
public static class SimpleTelephonyConnection extends TelephonyConnection {
public boolean wasDisconnected = false;
public boolean wasUnheld = false;
+ public boolean wasHeld = false;
@Override
public TelephonyConnection cloneConnection() {
@@ -137,6 +141,11 @@
public void onUnhold() {
wasUnheld = true;
}
+
+ @Override
+ public void onHold() {
+ wasHeld = true;
+ }
}
public static class SimpleConference extends Conference {
@@ -190,6 +199,8 @@
@Mock EmergencyCallDomainSelectionConnection mEmergencyCallDomainSelectionConnection;
@Mock NormalCallDomainSelectionConnection mNormalCallDomainSelectionConnection;
@Mock ImsPhone mImsPhone;
+ @Mock
+ private SatelliteSOSMessageRecommender mSatelliteSOSMessageRecommender;
private EmergencyStateTracker mEmergencyStateTracker;
private Phone mPhone0;
private Phone mPhone1;
@@ -216,6 +227,7 @@
@Before
public void setUp() throws Exception {
super.setUp();
+ doReturn(Looper.getMainLooper()).when(mContext).getMainLooper();
mTestConnectionService = new TestTelephonyConnectionService(mContext);
mTestConnectionService.setPhoneFactoryProxy(mPhoneFactoryProxy);
mTestConnectionService.setSubscriptionManagerProxy(mSubscriptionManagerProxy);
@@ -241,6 +253,11 @@
mEmergencyStateTracker = Mockito.mock(EmergencyStateTracker.class);
replaceInstance(TelephonyConnectionService.class, "mEmergencyStateTracker",
mTestConnectionService, mEmergencyStateTracker);
+ replaceInstance(TelephonyConnectionService.class, "mSatelliteSOSMessageRecommender",
+ mTestConnectionService, mSatelliteSOSMessageRecommender);
+ doNothing().when(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), any());
+ doNothing().when(mSatelliteSOSMessageRecommender).onEmergencyCallConnectionStateChanged(
+ anyString(), anyInt());
doReturn(CompletableFuture.completedFuture(NOT_DISCONNECTED))
.when(mEmergencyStateTracker)
.startEmergencyCall(any(), anyString(), eq(false));
@@ -1262,6 +1279,7 @@
// This shouldn't happen
fail();
}
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), any());
}
/**
@@ -1666,6 +1684,24 @@
}
/**
+ * For DSDA devices, placing an outgoing call on a 2nd sub will hold the existing connection on
+ * the first sub.
+ */
+ @Test
+ @SmallTest
+ public void testHoldOnOtherSubForVirtualDsdaDevice() {
+ when(mTelephonyManagerProxy.isConcurrentCallsPossible()).thenReturn(true);
+
+ ArrayList<android.telecom.Connection> tcs = new ArrayList<>();
+ SimpleTelephonyConnection tc1 = createTestConnection(SUB1_HANDLE, 0, false);
+ tcs.add(tc1);
+ Conferenceable c = TelephonyConnectionService.maybeHoldCallsOnOtherSubs(
+ tcs, new ArrayList<>(), SUB2_HANDLE, mTelephonyManagerProxy);
+ assertTrue(c.equals(tc1));
+ assertTrue(tc1.wasHeld);
+ }
+
+ /**
* Verifies that TelephonyManager is used to determine whether a connection is Emergency when
* creating an outgoing connection.
*/
@@ -1699,6 +1735,7 @@
.getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
verify(mEmergencyStateTracker)
.startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), eq(mPhone0));
verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
@@ -1728,6 +1765,7 @@
.getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
verify(mEmergencyStateTracker)
.startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), eq(mPhone0));
verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
@@ -1758,6 +1796,7 @@
verify(mEmergencyStateTracker, times(1))
.startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), eq(mPhone0));
verify(mDomainSelectionResolver, times(0))
.getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
verify(mEmergencyCallDomainSelectionConnection, times(0))
@@ -1851,6 +1890,7 @@
verify(mDomainSelectionResolver)
.getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(false));
verify(mNormalCallDomainSelectionConnection).createNormalConnection(any(), any());
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), eq(mPhone0));
ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
@@ -1888,6 +1928,7 @@
.getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
verify(mEmergencyStateTracker)
.startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), eq(mPhone0));
verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
@@ -1928,6 +1969,7 @@
.getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
verify(mEmergencyStateTracker)
.startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), eq(mPhone0));
verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
@@ -2191,6 +2233,7 @@
verify(mEmergencyStateTracker)
.startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), eq(mPhone0));
// dialing is canceled
mTestConnectionService.onLocalHangup(c);
@@ -2258,6 +2301,7 @@
.getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
verify(mEmergencyStateTracker)
.startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), eq(mPhone0));
verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
verify(mPhone0).dial(anyString(), any(), any());
@@ -2269,6 +2313,8 @@
TelephonyConnection.TelephonyConnectionListener connectionListener =
mTestConnectionService.getEmergencyConnectionListener();
+ TelephonyConnection.TelephonyConnectionListener connectionSatelliteListener =
+ mTestConnectionService.getEmergencyConnectionSatelliteListener();
connectionListener.onOriginalConnectionConfigured(c);
@@ -2277,32 +2323,44 @@
verify(mEmergencyStateTracker, times(0)).onEmergencyCallStateChanged(
any(), eq(TELECOM_CALL_ID1));
+ verify(mSatelliteSOSMessageRecommender, times(0))
+ .onEmergencyCallConnectionStateChanged(eq(TELECOM_CALL_ID1), anyInt());
c.setActive();
doReturn(Call.State.ACTIVE).when(orgConn).getState();
connectionListener.onStateChanged(c, c.getState());
+ connectionSatelliteListener.onStateChanged(c, c.getState());
// ACTIVE sate is notified
verify(mEmergencyStateTracker, times(1)).onEmergencyCallStateChanged(
eq(Call.State.ACTIVE), eq(TELECOM_CALL_ID1));
+ verify(mSatelliteSOSMessageRecommender, times(1))
+ .onEmergencyCallConnectionStateChanged(eq(TELECOM_CALL_ID1),
+ eq(android.telecom.Connection.STATE_ACTIVE));
// state change to HOLDING
c.setOnHold();
doReturn(Call.State.HOLDING).when(orgConn).getState();
connectionListener.onStateChanged(c, c.getState());
+ connectionSatelliteListener.onStateChanged(c, c.getState());
// state change not notified any more after CONNECTED once
verify(mEmergencyStateTracker, times(1)).onEmergencyCallStateChanged(
any(), eq(TELECOM_CALL_ID1));
+ verify(mSatelliteSOSMessageRecommender, times(1))
+ .onEmergencyCallConnectionStateChanged(eq(TELECOM_CALL_ID1), anyInt());
// state change to ACTIVE again
c.setActive();
doReturn(Call.State.ACTIVE).when(orgConn).getState();
connectionListener.onStateChanged(c, c.getState());
+ connectionSatelliteListener.onStateChanged(c, c.getState());
// state change not notified any more after CONNECTED once
verify(mEmergencyStateTracker, times(1)).onEmergencyCallStateChanged(
any(), eq(TELECOM_CALL_ID1));
+ verify(mSatelliteSOSMessageRecommender, times(1))
+ .onEmergencyCallConnectionStateChanged(eq(TELECOM_CALL_ID1), anyInt());
// SRVCC happens
c.setIsImsConnection(false);
@@ -2318,10 +2376,13 @@
c.setDisconnected(null);
doReturn(Call.State.DISCONNECTED).when(orgConn).getState();
connectionListener.onStateChanged(c, c.getState());
+ connectionSatelliteListener.onStateChanged(c, c.getState());
// state change not notified
verify(mEmergencyStateTracker, times(1)).onEmergencyCallStateChanged(
any(), eq(TELECOM_CALL_ID1));
+ verify(mSatelliteSOSMessageRecommender, times(1))
+ .onEmergencyCallConnectionStateChanged(eq(TELECOM_CALL_ID1), anyInt());
}
@Test
@@ -2386,6 +2447,7 @@
verify(mDomainSelectionResolver)
.getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(false));
verify(mNormalCallDomainSelectionConnection).createNormalConnection(any(), any());
+ verify(mSatelliteSOSMessageRecommender, never()).onEmergencyCallStarted(any(), any());
ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
@@ -2410,6 +2472,7 @@
verify(mDomainSelectionResolver)
.getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(false));
verify(mNormalCallDomainSelectionConnection).createNormalConnection(any(), any());
+ verify(mSatelliteSOSMessageRecommender, never()).onEmergencyCallStarted(any(), any());
ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
diff --git a/tests/src/com/android/services/telephony/TelephonyConnectionTest.java b/tests/src/com/android/services/telephony/TelephonyConnectionTest.java
index 758ded7..bf9fa01 100644
--- a/tests/src/com/android/services/telephony/TelephonyConnectionTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConnectionTest.java
@@ -1,6 +1,9 @@
package com.android.services.telephony;
import static android.telecom.Connection.STATE_DISCONNECTED;
+import static android.telephony.ims.ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED;
+import static android.telephony.ims.ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL;
+import static android.telephony.ims.ImsReasonInfo.EXTRA_CODE_CALL_RETRY_EMERGENCY;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
@@ -23,6 +26,8 @@
import android.telecom.Connection;
import android.telephony.CarrierConfigManager;
import android.telephony.DisconnectCause;
+import android.telephony.emergency.EmergencyNumber;
+import android.telephony.ims.ImsReasonInfo;
import androidx.test.runner.AndroidJUnit4;
@@ -39,6 +44,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+
@RunWith(AndroidJUnit4.class)
public class TelephonyConnectionTest {
@Mock
@@ -306,4 +313,58 @@
assertNotEquals(STATE_DISCONNECTED, c.getState());
assertTrue(c.isOriginalConnectionCleared());
}
+
+ @Test
+ public void testDomainSelectionDisconnected_AlternateService() {
+ TestTelephonyConnection c = new TestTelephonyConnection();
+ c.setOriginalConnection(mImsPhoneConnection);
+ c.setIsImsConnection(true);
+ doReturn(Call.State.DISCONNECTED).when(mImsPhoneConnection)
+ .getState();
+ doReturn(new ImsReasonInfo(CODE_SIP_ALTERNATE_EMERGENCY_CALL, 0, null))
+ .when(mImsPhoneConnection).getImsReasonInfo();
+ doReturn(getEmergencyNumber(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE))
+ .when(mImsPhoneConnection).getEmergencyNumberInfo();
+ c.setTelephonyConnectionService(mTelephonyConnectionService);
+ doReturn(true).when(mTelephonyConnectionService)
+ .maybeReselectDomain(any(), anyInt(), any());
+ c.updateState();
+
+ Integer serviceCategory = c.getEmergencyServiceCategory();
+
+ assertNotNull(serviceCategory);
+ assertEquals(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE,
+ serviceCategory.intValue());
+ }
+
+ @Test
+ public void testDomainSelectionDisconnected_SilentRedialEmergency() {
+ TestTelephonyConnection c = new TestTelephonyConnection();
+ c.setOriginalConnection(mImsPhoneConnection);
+ c.setIsImsConnection(true);
+ doReturn(Call.State.DISCONNECTED).when(mImsPhoneConnection)
+ .getState();
+ doReturn(new ImsReasonInfo(CODE_LOCAL_CALL_CS_RETRY_REQUIRED,
+ EXTRA_CODE_CALL_RETRY_EMERGENCY, null))
+ .when(mImsPhoneConnection).getImsReasonInfo();
+ doReturn(getEmergencyNumber(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE))
+ .when(mImsPhoneConnection).getEmergencyNumberInfo();
+ c.setTelephonyConnectionService(mTelephonyConnectionService);
+ doReturn(true).when(mTelephonyConnectionService)
+ .maybeReselectDomain(any(), anyInt(), any());
+ c.updateState();
+
+ Integer serviceCategory = c.getEmergencyServiceCategory();
+
+ assertNotNull(serviceCategory);
+ assertEquals(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE,
+ serviceCategory.intValue());
+ }
+
+ private EmergencyNumber getEmergencyNumber(int eccCategory) {
+ return new EmergencyNumber("", "", "", eccCategory,
+ new ArrayList<String>(),
+ EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+ }
}