aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/java/com/android/internal/telephony/CallTracker.java18
-rw-r--r--src/java/com/android/internal/telephony/GsmCdmaCallTracker.java5
-rw-r--r--src/java/com/android/internal/telephony/GsmCdmaPhone.java93
-rw-r--r--src/java/com/android/internal/telephony/NetworkIndication.java4
-rw-r--r--src/java/com/android/internal/telephony/NetworkResponse.java4
-rw-r--r--src/java/com/android/internal/telephony/NetworkTypeController.java232
-rw-r--r--src/java/com/android/internal/telephony/Phone.java6
-rw-r--r--src/java/com/android/internal/telephony/PhoneConfigurationManager.java117
-rw-r--r--src/java/com/android/internal/telephony/RILUtils.java12
-rw-r--r--src/java/com/android/internal/telephony/SMSDispatcher.java8
-rw-r--r--src/java/com/android/internal/telephony/SimResponse.java5
-rw-r--r--src/java/com/android/internal/telephony/TelephonyComponentFactory.java22
-rw-r--r--src/java/com/android/internal/telephony/data/AutoDataSwitchController.java285
-rw-r--r--src/java/com/android/internal/telephony/data/DataConfigManager.java33
-rw-r--r--src/java/com/android/internal/telephony/data/DataEvaluation.java8
-rw-r--r--src/java/com/android/internal/telephony/data/DataNetwork.java21
-rw-r--r--src/java/com/android/internal/telephony/data/DataNetworkController.java145
-rw-r--r--src/java/com/android/internal/telephony/data/DataProfileManager.java11
-rw-r--r--src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java149
-rw-r--r--src/java/com/android/internal/telephony/domainselection/DomainSelectionController.java7
-rw-r--r--src/java/com/android/internal/telephony/domainselection/DomainSelectionResolver.java12
-rw-r--r--src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java26
-rw-r--r--src/java/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnection.java6
-rw-r--r--src/java/com/android/internal/telephony/domainselection/OWNERS1
-rw-r--r--src/java/com/android/internal/telephony/domainselection/SmsDomainSelectionConnection.java11
-rw-r--r--src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java220
-rw-r--r--src/java/com/android/internal/telephony/emergency/RadioOnHelper.java2
-rw-r--r--src/java/com/android/internal/telephony/euicc/EuiccConnector.java33
-rw-r--r--src/java/com/android/internal/telephony/euicc/EuiccController.java245
-rw-r--r--src/java/com/android/internal/telephony/imsphone/ImsNrSaModeHandler.java7
-rw-r--r--src/java/com/android/internal/telephony/imsphone/ImsPhone.java14
-rw-r--r--src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java19
-rwxr-xr-xsrc/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java37
-rw-r--r--src/java/com/android/internal/telephony/metrics/MetricsCollector.java2
-rw-r--r--src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java2
-rw-r--r--src/java/com/android/internal/telephony/satellite/DatagramController.java69
-rw-r--r--src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java129
-rw-r--r--src/java/com/android/internal/telephony/satellite/SatelliteController.java370
-rw-r--r--src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java29
-rw-r--r--src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java14
-rw-r--r--src/java/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifier.java66
-rw-r--r--src/java/com/android/internal/telephony/security/CellularNetworkSecuritySafetySource.java371
-rw-r--r--src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java100
-rw-r--r--src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java123
-rw-r--r--src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java126
45 files changed, 2752 insertions, 467 deletions
diff --git a/src/java/com/android/internal/telephony/CallTracker.java b/src/java/com/android/internal/telephony/CallTracker.java
index 38c6672bac..5e617f98f5 100644
--- a/src/java/com/android/internal/telephony/CallTracker.java
+++ b/src/java/com/android/internal/telephony/CallTracker.java
@@ -16,6 +16,7 @@
package com.android.internal.telephony;
+import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.AsyncResult;
@@ -25,8 +26,11 @@ import android.os.Message;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
import android.text.TextUtils;
+import com.android.internal.telephony.flags.FeatureFlags;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -55,6 +59,9 @@ public abstract class CallTracker extends Handler {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
protected boolean mNumberConverted = false;
+
+ protected final @NonNull FeatureFlags mFeatureFlags;
+
private final int VALID_COMPARE_LENGTH = 3;
//***** Events
@@ -77,7 +84,8 @@ public abstract class CallTracker extends Handler {
protected static final int EVENT_THREE_WAY_DIAL_BLANK_FLASH = 20;
@UnsupportedAppUsage
- public CallTracker() {
+ public CallTracker(FeatureFlags featureFlags) {
+ mFeatureFlags = featureFlags;
}
protected void pollCallsWhenSafe() {
@@ -91,6 +99,14 @@ public abstract class CallTracker extends Handler {
protected void
pollCallsAfterDelay() {
+ if (mFeatureFlags.preventInvocationRepeatOfRilCallWhenDeviceDoesNotSupportVoice()) {
+ if (!mCi.getHalVersion(TelephonyManager.HAL_SERVICE_VOICE)
+ .greaterOrEqual(RIL.RADIO_HAL_VERSION_1_4)) {
+ log("Skip polling because HAL_SERVICE_VOICE < RADIO_HAL_VERSION_1.4");
+ return;
+ }
+ }
+
Message msg = obtainMessage();
msg.what = EVENT_REPOLL_AFTER_DELAY;
diff --git a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
index d76ee199fc..5517bc6f4e 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
@@ -47,6 +47,7 @@ import com.android.internal.telephony.PhoneInternalInterface.DialArgs;
import com.android.internal.telephony.cdma.CdmaCallWaitingNotification;
import com.android.internal.telephony.domainselection.DomainSelectionResolver;
import com.android.internal.telephony.emergency.EmergencyStateTracker;
+import com.android.internal.telephony.flags.FeatureFlags;
import com.android.internal.telephony.metrics.TelephonyMetrics;
import com.android.telephony.Rlog;
@@ -155,7 +156,9 @@ public class GsmCdmaCallTracker extends CallTracker {
//***** Constructors
- public GsmCdmaCallTracker (GsmCdmaPhone phone) {
+ public GsmCdmaCallTracker(GsmCdmaPhone phone, FeatureFlags featureFlags) {
+ super(featureFlags);
+
this.mPhone = phone;
mCi = phone.mCi;
mCi.registerForCallStateChanged(this, EVENT_CALL_STATE_CHANGE, null);
diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
index 7e2143a54f..aca759b703 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
@@ -16,10 +16,7 @@
package com.android.internal.telephony;
-import static android.telephony.NetworkRegistrationInfo.DOMAIN_CS;
-import static android.telephony.NetworkRegistrationInfo.DOMAIN_CS_PS;
import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
-import static android.telephony.NetworkRegistrationInfo.DOMAIN_UNKNOWN;
import static com.android.internal.telephony.CommandException.Error.GENERIC_FAILURE;
import static com.android.internal.telephony.CommandException.Error.SIM_BUSY;
@@ -75,7 +72,6 @@ import android.telecom.VideoProfile;
import android.telephony.AccessNetworkConstants.TransportType;
import android.telephony.Annotation.DataActivityType;
import android.telephony.Annotation.RadioPowerState;
-import android.telephony.AnomalyReporter;
import android.telephony.BarringInfo;
import android.telephony.CarrierConfigManager;
import android.telephony.CellBroadcastIdRange;
@@ -96,6 +92,7 @@ import android.telephony.UiccAccessRule;
import android.telephony.UssdResponse;
import android.telephony.ims.ImsCallProfile;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
@@ -119,6 +116,7 @@ import com.android.internal.telephony.imsphone.ImsPhoneMmiCode;
import com.android.internal.telephony.metrics.TelephonyMetrics;
import com.android.internal.telephony.metrics.VoiceCallSessionStats;
import com.android.internal.telephony.security.CellularIdentifierDisclosureNotifier;
+import com.android.internal.telephony.security.CellularNetworkSecuritySafetySource;
import com.android.internal.telephony.security.NullCipherNotifier;
import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
import com.android.internal.telephony.subscription.SubscriptionManagerService.SubscriptionManagerServiceCallback;
@@ -150,7 +148,6 @@ import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
-import java.util.UUID;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -304,15 +301,24 @@ public class GsmCdmaPhone extends Phone {
private final SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionsChangedListener;
private final CallWaitingController mCallWaitingController;
+ private CellularNetworkSecuritySafetySource mSafetySource;
private CellularIdentifierDisclosureNotifier mIdentifierDisclosureNotifier;
private NullCipherNotifier mNullCipherNotifier;
+ /**
+ * Temporary placeholder variables until b/312788638 is resolved, whereupon these should be
+ * ported to TelephonyManager.
+ */
// Set via Carrier Config
- private boolean mIsN1ModeAllowedByCarrier = true;
+ private static final Integer N1_MODE_DISALLOWED_REASON_CARRIER = 1;
// Set via a call to the method on Phone; the only caller is IMS, and all of this code will
// need to be updated to a voting mechanism (...enabled for reason...) if additional callers
// are desired.
- private boolean mIsN1ModeAllowedByIms = true;
+ private static final Integer N1_MODE_DISALLOWED_REASON_IMS = 2;
+
+ // Set of use callers/reasons why N1 Mode is disallowed. If the set is empty, it's allowed.
+ private final Set<Integer> mN1ModeDisallowedReasons = new ArraySet<>();
+
// If this value is null, then the modem value is unknown. If a caller explicitly sets the
// N1 mode, this value will be initialized before any attempt to set the value in the modem.
private Boolean mModemN1Mode = null;
@@ -464,7 +470,7 @@ public class GsmCdmaPhone extends Phone {
}
mCT = mTelephonyComponentFactory.inject(GsmCdmaCallTracker.class.getName())
- .makeGsmCdmaCallTracker(this);
+ .makeGsmCdmaCallTracker(this, mFeatureFlags);
mIccPhoneBookIntManager = mTelephonyComponentFactory
.inject(IccPhoneBookInterfaceManager.class.getName())
.makeIccPhoneBookInterfaceManager(this);
@@ -525,6 +531,12 @@ public class GsmCdmaPhone extends Phone {
mCi.registerForImeiMappingChanged(this, EVENT_IMEI_MAPPING_CHANGED, null);
+ if (mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents()
+ || mFeatureFlags.enableModemCipherTransparencyUnsolEvents()) {
+ mSafetySource =
+ mTelephonyComponentFactory.makeCellularNetworkSecuritySafetySource(mContext);
+ }
+
if (mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents()) {
logi(
"enable_identifier_disclosure_transparency_unsol_events is on. Registering for "
@@ -533,7 +545,7 @@ public class GsmCdmaPhone extends Phone {
mIdentifierDisclosureNotifier =
mTelephonyComponentFactory
.inject(CellularIdentifierDisclosureNotifier.class.getName())
- .makeIdentifierDisclosureNotifier();
+ .makeIdentifierDisclosureNotifier(mSafetySource);
mCi.registerForCellularIdentifierDisclosures(
this, EVENT_CELL_IDENTIFIER_DISCLOSURE, null);
}
@@ -1486,24 +1498,6 @@ public class GsmCdmaPhone extends Phone {
&& (isWpsCall ? allowWpsOverIms : true);
Bundle extras = dialArgs.intentExtras;
- if (extras != null && extras.containsKey(PhoneConstants.EXTRA_COMPARE_DOMAIN)) {
- int domain = extras.getInt(PhoneConstants.EXTRA_DIAL_DOMAIN);
- if (!isEmergency && (!isMmiCode || isPotentialUssdCode)) {
- if ((domain == DOMAIN_PS && !useImsForCall)
- || (domain == DOMAIN_CS && useImsForCall)
- || domain == DOMAIN_UNKNOWN || domain == DOMAIN_CS_PS) {
- loge("[Anomaly] legacy-useImsForCall:" + useImsForCall
- + ", NCDS-domain:" + domain);
-
- AnomalyReporter.reportAnomaly(
- UUID.fromString("bfae6c2e-ca2f-4121-b167-9cad26a3b353"),
- "Domain selection results don't match. useImsForCall:"
- + useImsForCall + ", NCDS-domain:" + domain);
- }
- }
- extras.remove(PhoneConstants.EXTRA_COMPARE_DOMAIN);
- }
-
// Only when the domain selection service is supported, EXTRA_DIAL_DOMAIN extra shall exist.
if (extras != null && extras.containsKey(PhoneConstants.EXTRA_DIAL_DOMAIN)) {
int domain = extras.getInt(PhoneConstants.EXTRA_DIAL_DOMAIN);
@@ -2426,7 +2420,11 @@ public class GsmCdmaPhone extends Phone {
// This might be called by IMS on another thread, so to avoid the requirement to
// lock, post it through the handler.
post(() -> {
- mIsN1ModeAllowedByIms = enable;
+ if (enable) {
+ mN1ModeDisallowedReasons.remove(N1_MODE_DISALLOWED_REASON_IMS);
+ } else {
+ mN1ModeDisallowedReasons.add(N1_MODE_DISALLOWED_REASON_IMS);
+ }
if (mModemN1Mode == null) {
mCi.isN1ModeEnabled(obtainMessage(EVENT_GET_N1_MODE_ENABLED_DONE, result));
} else {
@@ -2440,7 +2438,7 @@ public class GsmCdmaPhone extends Phone {
/** Only called on the handler thread. */
private void maybeUpdateModemN1Mode(@Nullable Message result) {
- final boolean wantN1Enabled = mIsN1ModeAllowedByCarrier && mIsN1ModeAllowedByIms;
+ final boolean wantN1Enabled = mN1ModeDisallowedReasons.isEmpty();
logd("N1 Mode: isModemN1Enabled=" + mModemN1Mode + ", wantN1Enabled=" + wantN1Enabled);
@@ -2466,8 +2464,15 @@ public class GsmCdmaPhone extends Phone {
final int[] supportedNrModes = b.getIntArray(
CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY);
- mIsN1ModeAllowedByCarrier = ArrayUtils.contains(
- supportedNrModes, CarrierConfigManager.CARRIER_NR_AVAILABILITY_SA);
+
+ if (ArrayUtils.contains(
+ supportedNrModes,
+ CarrierConfigManager.CARRIER_NR_AVAILABILITY_SA)) {
+ mN1ModeDisallowedReasons.remove(N1_MODE_DISALLOWED_REASON_CARRIER);
+ } else {
+ mN1ModeDisallowedReasons.add(N1_MODE_DISALLOWED_REASON_CARRIER);
+ }
+
if (mModemN1Mode == null) {
mCi.isN1ModeEnabled(obtainMessage(EVENT_GET_N1_MODE_ENABLED_DONE));
} else {
@@ -2602,7 +2607,7 @@ public class GsmCdmaPhone extends Phone {
Bundle extras = new Bundle();
extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
- final TelecomManager telecomManager = TelecomManager.from(mContext);
+ final TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
telecomManager.placeCall(
Uri.fromParts(PhoneAccount.SCHEME_TEL, cfNumber, null), extras);
@@ -2863,7 +2868,7 @@ public class GsmCdmaPhone extends Phone {
Bundle extras = new Bundle();
extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
- final TelecomManager telecomManager = TelecomManager.from(mContext);
+ final TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
telecomManager.placeCall(
Uri.fromParts(PhoneAccount.SCHEME_TEL, cwPrefix, null), extras);
@@ -3717,7 +3722,7 @@ public class GsmCdmaPhone extends Phone {
if (mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents()
&& mIdentifierDisclosureNotifier != null
&& disclosure != null) {
- mIdentifierDisclosureNotifier.addDisclosure(getSubId(), disclosure);
+ mIdentifierDisclosureNotifier.addDisclosure(mContext, getSubId(), disclosure);
}
break;
@@ -5002,7 +5007,7 @@ public class GsmCdmaPhone extends Phone {
}
private PhoneAccountHandle subscriptionIdToPhoneAccountHandle(final int subId) {
- final TelecomManager telecomManager = TelecomManager.from(mContext);
+ final TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
final TelephonyManager telephonyManager = TelephonyManager.from(mContext);
final Iterator<PhoneAccountHandle> phoneAccounts =
telecomManager.getCallCapablePhoneAccounts(true).listIterator();
@@ -5360,11 +5365,11 @@ public class GsmCdmaPhone extends Phone {
// enable/disable API, so we only toggle the enable state if the unsol events feature
// flag is enabled.
if (mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents()) {
- if (prefEnabled) {
- mIdentifierDisclosureNotifier.enable();
- } else {
- mIdentifierDisclosureNotifier.disable();
- }
+ if (prefEnabled) {
+ mIdentifierDisclosureNotifier.enable(mContext);
+ } else {
+ mIdentifierDisclosureNotifier.disable(mContext);
+ }
} else {
logi("Not toggling enable state for disclosure notifier. Feature flag "
+ "enable_identifier_disclosure_transparency_unsol_events is disabled");
@@ -5415,4 +5420,12 @@ public class GsmCdmaPhone extends Phone {
public boolean isNullCipherNotificationSupported() {
return mIsNullCipherNotificationSupported;
}
+
+ @Override
+ public void refreshSafetySources(String refreshBroadcastId) {
+ if (mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents()
+ || mFeatureFlags.enableModemCipherTransparencyUnsolEvents()) {
+ mSafetySource.refresh(mContext, refreshBroadcastId);
+ }
+ }
}
diff --git a/src/java/com/android/internal/telephony/NetworkIndication.java b/src/java/com/android/internal/telephony/NetworkIndication.java
index e847060c95..5c492349cd 100644
--- a/src/java/com/android/internal/telephony/NetworkIndication.java
+++ b/src/java/com/android/internal/telephony/NetworkIndication.java
@@ -45,7 +45,7 @@ import android.telephony.BarringInfo;
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
import android.telephony.CellularIdentifierDisclosure;
-import android.telephony.EmergencyRegResult;
+import android.telephony.EmergencyRegistrationResult;
import android.telephony.LinkCapacityEstimate;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.PhysicalChannelConfig;
@@ -415,7 +415,7 @@ public class NetworkIndication extends IRadioNetworkIndication.Stub {
android.hardware.radio.network.EmergencyRegResult result) {
mRil.processIndication(HAL_SERVICE_NETWORK, indicationType);
- EmergencyRegResult response = RILUtils.convertHalEmergencyRegResult(result);
+ EmergencyRegistrationResult response = RILUtils.convertHalEmergencyRegResult(result);
if (mRil.isLogOrTrace()) {
mRil.unsljLogRet(RIL_UNSOL_EMERGENCY_NETWORK_SCAN_RESULT, response);
diff --git a/src/java/com/android/internal/telephony/NetworkResponse.java b/src/java/com/android/internal/telephony/NetworkResponse.java
index eb2cd16cca..b4a37b3d60 100644
--- a/src/java/com/android/internal/telephony/NetworkResponse.java
+++ b/src/java/com/android/internal/telephony/NetworkResponse.java
@@ -24,7 +24,7 @@ import android.hardware.radio.network.IRadioNetworkResponse;
import android.os.AsyncResult;
import android.telephony.BarringInfo;
import android.telephony.CellInfo;
-import android.telephony.EmergencyRegResult;
+import android.telephony.EmergencyRegistrationResult;
import android.telephony.RadioAccessSpecifier;
import android.telephony.SignalStrength;
@@ -429,7 +429,7 @@ public class NetworkResponse extends IRadioNetworkResponse.Stub {
RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo);
if (rr != null) {
- EmergencyRegResult response = RILUtils.convertHalEmergencyRegResult(regState);
+ EmergencyRegistrationResult response = RILUtils.convertHalEmergencyRegResult(regState);
if (responseInfo.error == RadioError.NONE) {
RadioResponse.sendMessageResponse(rr.mResult, response);
}
diff --git a/src/java/com/android/internal/telephony/NetworkTypeController.java b/src/java/com/android/internal/telephony/NetworkTypeController.java
index ea7a6de6a4..b9ad388bbf 100644
--- a/src/java/com/android/internal/telephony/NetworkTypeController.java
+++ b/src/java/com/android/internal/telephony/NetworkTypeController.java
@@ -37,6 +37,9 @@ import android.telephony.TelephonyDisplayInfo;
import android.telephony.TelephonyManager;
import android.telephony.data.DataCallResponse;
import android.telephony.data.DataCallResponse.LinkStatus;
+import android.telephony.data.EpsQos;
+import android.telephony.data.NrQos;
+import android.telephony.data.QosBearerSession;
import android.text.TextUtils;
import com.android.internal.telephony.data.DataNetworkController.DataNetworkControllerCallback;
@@ -112,8 +115,10 @@ public class NetworkTypeController extends StateMachine {
private static final int EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED = 11;
/** Event for device idle mode changed, when device goes to deep sleep and pauses all timers. */
private static final int EVENT_DEVICE_IDLE_MODE_CHANGED = 12;
+ /** Event for qos sessions changed. */
+ private static final int EVENT_QOS_SESSION_CHANGED = 13;
- private static final String[] sEvents = new String[EVENT_DEVICE_IDLE_MODE_CHANGED + 1];
+ private static final String[] sEvents = new String[EVENT_QOS_SESSION_CHANGED + 1];
static {
sEvents[EVENT_UPDATE] = "EVENT_UPDATE";
sEvents[EVENT_QUIT] = "EVENT_QUIT";
@@ -129,6 +134,7 @@ public class NetworkTypeController extends StateMachine {
sEvents[EVENT_INITIALIZE] = "EVENT_INITIALIZE";
sEvents[EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED] = "EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED";
sEvents[EVENT_DEVICE_IDLE_MODE_CHANGED] = "EVENT_DEVICE_IDLE_MODE_CHANGED";
+ sEvents[EVENT_QOS_SESSION_CHANGED] = "EVENT_QOS_SESSION_CHANGED";
}
@NonNull private final Phone mPhone;
@@ -158,6 +164,30 @@ public class NetworkTypeController extends StateMachine {
}
};
+ @NonNull private final DataNetworkControllerCallback mDataNetworkControllerCallback =
+ new DataNetworkControllerCallback(getHandler()::post) {
+ @Override
+ public void onQosSessionsChanged(
+ @NonNull List<QosBearerSession> qosBearerSessions) {
+ if (!mIsTimerResetEnabledOnVoiceQos) return;
+ sendMessage(obtainMessage(EVENT_QOS_SESSION_CHANGED, qosBearerSessions));
+ }
+
+ @Override
+ public void onNrAdvancedCapableByPcoChanged(boolean nrAdvancedCapable) {
+ if (mNrAdvancedCapablePcoId <= 0) return;
+ log("mIsNrAdvancedAllowedByPco=" + nrAdvancedCapable);
+ mIsNrAdvancedAllowedByPco = nrAdvancedCapable;
+ sendMessage(EVENT_UPDATE);
+ }
+
+ @Override
+ public void onPhysicalLinkStatusChanged(@LinkStatus int status) {
+ if (isUsingPhysicalChannelConfigForRrcDetection()) return;
+ sendMessage(obtainMessage(EVENT_PHYSICAL_LINK_STATUS_CHANGED, status));
+ }
+ };
+
@NonNull private Map<String, OverrideTimerRule> mOverrideTimerRules = new HashMap<>();
@NonNull private String mLteEnhancedPattern = "";
@Annotation.OverrideNetworkType private int mOverrideNetworkType;
@@ -165,6 +195,10 @@ public class NetworkTypeController extends StateMachine {
private boolean mIsPrimaryTimerActive;
private boolean mIsSecondaryTimerActive;
private boolean mIsTimerResetEnabledForLegacyStateRrcIdle;
+ /** Carrier config to reset timers when mccmnc changes */
+ private boolean mIsTimerResetEnabledOnPlmnChanges;
+ /** Carrier config to reset timers when QCI(LTE) or 5QI(NR) is 1(conversational voice) */
+ private boolean mIsTimerResetEnabledOnVoiceQos;
private int mLtePlusThresholdBandwidth;
private int mNrAdvancedThresholdBandwidth;
private boolean mIncludeLteForNrAdvancedThresholdBandwidth;
@@ -172,6 +206,8 @@ public class NetworkTypeController extends StateMachine {
@NonNull private final Set<Integer> mAdditionalNrAdvancedBands = new HashSet<>();
@NonNull private String mPrimaryTimerState;
@NonNull private String mSecondaryTimerState;
+ // TODO(b/316425811 remove the workaround)
+ private int mNrAdvancedBandsSecondaryTimer;
@NonNull private String mPreviousState;
@LinkStatus private int mPhysicalLinkStatus;
private boolean mIsPhysicalChannelConfig16Supported;
@@ -182,15 +218,14 @@ public class NetworkTypeController extends StateMachine {
private boolean mIsDeviceIdleMode = false;
private boolean mPrimaryCellChangedWhileIdle = false;
- @Nullable private DataNetworkControllerCallback mNrAdvancedCapableByPcoChangedCallback = null;
- @Nullable private DataNetworkControllerCallback mNrPhysicalLinkStatusChangedCallback = null;
-
// Cached copies below to prevent race conditions
@NonNull private ServiceState mServiceState;
@Nullable private List<PhysicalChannelConfig> mPhysicalChannelConfigs;
// Ratchet physical channel config fields to prevent 5G/5G+ flickering
@NonNull private Set<Integer> mRatchetedNrBands = new HashSet<>();
+ // TODO(b/316425811 remove the workaround)
+ private boolean mLastShownNrDueToAdvancedBand = false;
private int mRatchetedNrBandwidths = 0;
private int mLastAnchorNrCellId = PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN;
private boolean mDoesPccListIndicateIdle = false;
@@ -271,6 +306,8 @@ public class NetworkTypeController extends StateMachine {
TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
mPhone.getDeviceStateMonitor().registerForPhysicalChannelConfigNotifChanged(getHandler(),
EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED, null);
+ mPhone.getDataNetworkController().registerDataNetworkControllerCallback(
+ mDataNetworkControllerCallback);
IntentFilter filter = new IntentFilter();
filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
mPhone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone);
@@ -283,6 +320,8 @@ public class NetworkTypeController extends StateMachine {
mPhone.unregisterForPreferredNetworkTypeChanged(getHandler());
mPhone.getServiceStateTracker().unregisterForServiceStateChanged(getHandler());
mPhone.getDeviceStateMonitor().unregisterForPhysicalChannelConfigNotifChanged(getHandler());
+ mPhone.getDataNetworkController().unregisterDataNetworkControllerCallback(
+ mDataNetworkControllerCallback);
mPhone.getContext().unregisterReceiver(mIntentReceiver);
CarrierConfigManager ccm = mPhone.getContext().getSystemService(CarrierConfigManager.class);
if (mCarrierConfigChangeListener != null) {
@@ -304,6 +343,10 @@ public class NetworkTypeController extends StateMachine {
CarrierConfigManager.KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING);
mIsTimerResetEnabledForLegacyStateRrcIdle = config.getBoolean(
CarrierConfigManager.KEY_NR_TIMERS_RESET_IF_NON_ENDC_AND_RRC_IDLE_BOOL);
+ mIsTimerResetEnabledOnPlmnChanges = config.getBoolean(
+ CarrierConfigManager.KEY_NR_TIMERS_RESET_ON_PLMN_CHANGE_BOOL);
+ mIsTimerResetEnabledOnVoiceQos = config.getBoolean(
+ CarrierConfigManager.KEY_NR_TIMERS_RESET_ON_VOICE_QOS_BOOL);
mLtePlusThresholdBandwidth = config.getInt(
CarrierConfigManager.KEY_LTE_PLUS_THRESHOLD_BANDWIDTH_KHZ_INT);
mNrAdvancedThresholdBandwidth = config.getInt(
@@ -322,43 +365,10 @@ public class NetworkTypeController extends StateMachine {
}
mNrAdvancedCapablePcoId = config.getInt(
CarrierConfigManager.KEY_NR_ADVANCED_CAPABLE_PCO_ID_INT);
- if (mNrAdvancedCapablePcoId > 0 && mNrAdvancedCapableByPcoChangedCallback == null) {
- mNrAdvancedCapableByPcoChangedCallback =
- new DataNetworkControllerCallback(getHandler()::post) {
- @Override
- public void onNrAdvancedCapableByPcoChanged(boolean nrAdvancedCapable) {
- log("mIsNrAdvancedAllowedByPco=" + nrAdvancedCapable);
- mIsNrAdvancedAllowedByPco = nrAdvancedCapable;
- sendMessage(EVENT_UPDATE);
- }
- };
- mPhone.getDataNetworkController().registerDataNetworkControllerCallback(
- mNrAdvancedCapableByPcoChangedCallback);
- } else if (mNrAdvancedCapablePcoId == 0 && mNrAdvancedCapableByPcoChangedCallback != null) {
- mPhone.getDataNetworkController().unregisterDataNetworkControllerCallback(
- mNrAdvancedCapableByPcoChangedCallback);
- mNrAdvancedCapableByPcoChangedCallback = null;
- }
mIsUsingUserDataForRrcDetection = config.getBoolean(
CarrierConfigManager.KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL);
- if (!isUsingPhysicalChannelConfigForRrcDetection()) {
- if (mNrPhysicalLinkStatusChangedCallback == null) {
- mNrPhysicalLinkStatusChangedCallback =
- new DataNetworkControllerCallback(getHandler()::post) {
- @Override
- public void onPhysicalLinkStatusChanged(@LinkStatus int status) {
- sendMessage(obtainMessage(EVENT_PHYSICAL_LINK_STATUS_CHANGED,
- new AsyncResult(null, status, null)));
- }
- };
- mPhone.getDataNetworkController().registerDataNetworkControllerCallback(
- mNrPhysicalLinkStatusChangedCallback);
- }
- } else if (mNrPhysicalLinkStatusChangedCallback != null) {
- mPhone.getDataNetworkController().unregisterDataNetworkControllerCallback(
- mNrPhysicalLinkStatusChangedCallback);
- mNrPhysicalLinkStatusChangedCallback = null;
- }
+ mNrAdvancedBandsSecondaryTimer = config.getInt(
+ CarrierConfigManager.KEY_NR_ADVANCED_BANDS_SECONDARY_TIMER_SECONDS_INT);
String nrIconConfiguration = config.getString(
CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING);
String overrideTimerRule = config.getString(
@@ -366,7 +376,8 @@ public class NetworkTypeController extends StateMachine {
String overrideSecondaryTimerRule = config.getString(
CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING);
createTimerRules(nrIconConfiguration, overrideTimerRule, overrideSecondaryTimerRule);
- updatePhysicalChannelConfigs();
+ updatePhysicalChannelConfigs(
+ mPhone.getServiceStateTracker().getPhysicalChannelConfigList());
}
private void createTimerRules(String icons, String timers, String secondaryTimers) {
@@ -598,6 +609,7 @@ public class NetworkTypeController extends StateMachine {
private final class DefaultState extends State {
@Override
public boolean processMessage(Message msg) {
+ AsyncResult ar;
if (DBG) log("DefaultState: process " + getEventName(msg.what));
switch (msg.what) {
case EVENT_UPDATE:
@@ -618,17 +630,15 @@ public class NetworkTypeController extends StateMachine {
parseCarrierConfigs();
break;
case EVENT_SERVICE_STATE_CHANGED:
- mServiceState = mPhone.getServiceStateTracker().getServiceState();
- if (DBG) log("ServiceState updated: " + mServiceState);
+ onServiceStateChanged();
transitionToCurrentState();
break;
case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
- AsyncResult ar = (AsyncResult) msg.obj;
- mPhysicalLinkStatus = (int) ar.result;
+ mPhysicalLinkStatus = msg.arg1;
break;
case EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED:
- AsyncResult result = (AsyncResult) msg.obj;
- mIsPhysicalChannelConfigOn = (boolean) result.result;
+ ar = (AsyncResult) msg.obj;
+ mIsPhysicalChannelConfigOn = (boolean) ar.result;
if (DBG) {
log("mIsPhysicalChannelConfigOn changed to: " + mIsPhysicalChannelConfigOn);
}
@@ -655,11 +665,17 @@ public class NetworkTypeController extends StateMachine {
mIsSecondaryTimerActive = false;
mSecondaryTimerState = "";
updateTimers();
+ mLastShownNrDueToAdvancedBand = false;
updateOverrideNetworkType();
break;
case EVENT_RADIO_OFF_OR_UNAVAILABLE:
if (DBG) log("Reset timers since radio is off or unavailable.");
resetAllTimers();
+ mRatchetedNrBands.clear();
+ mRatchetedNrBandwidths = 0;
+ mLastAnchorNrCellId = PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN;
+ mDoesPccListIndicateIdle = false;
+ mPhysicalChannelConfigs = null;
transitionTo(mLegacyState);
break;
case EVENT_PREFERRED_NETWORK_MODE_CHANGED:
@@ -668,7 +684,8 @@ public class NetworkTypeController extends StateMachine {
transitionToCurrentState();
break;
case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED:
- updatePhysicalChannelConfigs();
+ ar = (AsyncResult) msg.obj;
+ updatePhysicalChannelConfigs((List<PhysicalChannelConfig>) ar.result);
if (isUsingPhysicalChannelConfigForRrcDetection()) {
mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
}
@@ -686,6 +703,24 @@ public class NetworkTypeController extends StateMachine {
}
transitionToCurrentState();
break;
+ case EVENT_QOS_SESSION_CHANGED:
+ List<QosBearerSession> qosBearerSessions = (List<QosBearerSession>) msg.obj;
+ boolean inVoiceCall = false;
+ for (QosBearerSession session : qosBearerSessions) {
+ // TS 23.203 23.501 - 1 means conversational voice
+ if (session.getQos() instanceof EpsQos qos) {
+ inVoiceCall = qos.getQci() == 1;
+ } else if (session.getQos() instanceof NrQos qos) {
+ inVoiceCall = qos.get5Qi() == 1;
+ }
+ if (inVoiceCall) {
+ if (DBG) log("Device in voice call, reset all timers");
+ resetAllTimers();
+ transitionToCurrentState();
+ break;
+ }
+ }
+ break;
default:
throw new RuntimeException("Received invalid event: " + msg.what);
}
@@ -720,10 +755,10 @@ public class NetworkTypeController extends StateMachine {
public boolean processMessage(Message msg) {
if (DBG) log("LegacyState: process " + getEventName(msg.what));
updateTimers();
+ AsyncResult ar;
switch (msg.what) {
case EVENT_SERVICE_STATE_CHANGED:
- mServiceState = mPhone.getServiceStateTracker().getServiceState();
- if (DBG) log("ServiceState updated: " + mServiceState);
+ onServiceStateChanged();
// fallthrough
case EVENT_UPDATE:
int rat = getDataNetworkType();
@@ -748,18 +783,19 @@ public class NetworkTypeController extends StateMachine {
mIsNrRestricted = isNrRestricted();
break;
case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED:
- updatePhysicalChannelConfigs();
+ ar = (AsyncResult) msg.obj;
+ updatePhysicalChannelConfigs((List<PhysicalChannelConfig>) ar.result);
if (isUsingPhysicalChannelConfigForRrcDetection()) {
mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
if (mIsTimerResetEnabledForLegacyStateRrcIdle && !isPhysicalLinkActive()) {
if (DBG) log("Reset timers since timer reset is enabled for RRC idle.");
resetAllTimers();
+ updateOverrideNetworkType();
}
}
break;
case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
- AsyncResult ar = (AsyncResult) msg.obj;
- mPhysicalLinkStatus = (int) ar.result;
+ mPhysicalLinkStatus = msg.arg1;
if (mIsTimerResetEnabledForLegacyStateRrcIdle && !isPhysicalLinkActive()) {
if (DBG) log("Reset timers since timer reset is enabled for RRC idle.");
resetAllTimers();
@@ -801,10 +837,10 @@ public class NetworkTypeController extends StateMachine {
public boolean processMessage(Message msg) {
if (DBG) log("IdleState: process " + getEventName(msg.what));
updateTimers();
+ AsyncResult ar;
switch (msg.what) {
case EVENT_SERVICE_STATE_CHANGED:
- mServiceState = mPhone.getServiceStateTracker().getServiceState();
- if (DBG) log("ServiceState updated: " + mServiceState);
+ onServiceStateChanged();
// fallthrough
case EVENT_UPDATE:
int rat = getDataNetworkType();
@@ -829,7 +865,8 @@ public class NetworkTypeController extends StateMachine {
}
break;
case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED:
- updatePhysicalChannelConfigs();
+ ar = (AsyncResult) msg.obj;
+ updatePhysicalChannelConfigs((List<PhysicalChannelConfig>) ar.result);
if (isUsingPhysicalChannelConfigForRrcDetection()) {
mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
if (isPhysicalLinkActive()) {
@@ -841,8 +878,7 @@ public class NetworkTypeController extends StateMachine {
}
break;
case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
- AsyncResult ar = (AsyncResult) msg.obj;
- mPhysicalLinkStatus = (int) ar.result;
+ mPhysicalLinkStatus = msg.arg1;
if (isPhysicalLinkActive()) {
transitionWithTimerTo(mLteConnectedState);
} else {
@@ -885,10 +921,10 @@ public class NetworkTypeController extends StateMachine {
public boolean processMessage(Message msg) {
if (DBG) log("LteConnectedState: process " + getEventName(msg.what));
updateTimers();
+ AsyncResult ar;
switch (msg.what) {
case EVENT_SERVICE_STATE_CHANGED:
- mServiceState = mPhone.getServiceStateTracker().getServiceState();
- if (DBG) log("ServiceState updated: " + mServiceState);
+ onServiceStateChanged();
// fallthrough
case EVENT_UPDATE:
int rat = getDataNetworkType();
@@ -913,7 +949,8 @@ public class NetworkTypeController extends StateMachine {
}
break;
case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED:
- updatePhysicalChannelConfigs();
+ ar = (AsyncResult) msg.obj;
+ updatePhysicalChannelConfigs((List<PhysicalChannelConfig>) ar.result);
if (isUsingPhysicalChannelConfigForRrcDetection()) {
mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
if (!isPhysicalLinkActive()) {
@@ -925,8 +962,7 @@ public class NetworkTypeController extends StateMachine {
}
break;
case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
- AsyncResult ar = (AsyncResult) msg.obj;
- mPhysicalLinkStatus = (int) ar.result;
+ mPhysicalLinkStatus = msg.arg1;
if (!isPhysicalLinkActive()) {
transitionWithTimerTo(mIdleState);
} else {
@@ -969,10 +1005,10 @@ public class NetworkTypeController extends StateMachine {
public boolean processMessage(Message msg) {
if (DBG) log("NrIdleState: process " + getEventName(msg.what));
updateTimers();
+ AsyncResult ar;
switch (msg.what) {
case EVENT_SERVICE_STATE_CHANGED:
- mServiceState = mPhone.getServiceStateTracker().getServiceState();
- if (DBG) log("ServiceState updated: " + mServiceState);
+ onServiceStateChanged();
// fallthrough
case EVENT_UPDATE:
int rat = getDataNetworkType();
@@ -994,7 +1030,8 @@ public class NetworkTypeController extends StateMachine {
}
break;
case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED:
- updatePhysicalChannelConfigs();
+ ar = (AsyncResult) msg.obj;
+ updatePhysicalChannelConfigs((List<PhysicalChannelConfig>) ar.result);
if (isUsingPhysicalChannelConfigForRrcDetection()) {
mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
}
@@ -1006,8 +1043,7 @@ public class NetworkTypeController extends StateMachine {
}
break;
case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
- AsyncResult ar = (AsyncResult) msg.obj;
- mPhysicalLinkStatus = (int) ar.result;
+ mPhysicalLinkStatus = msg.arg1;
if (isPhysicalLinkActive()) {
transitionWithTimerTo(mNrConnectedState);
}
@@ -1047,10 +1083,10 @@ public class NetworkTypeController extends StateMachine {
public boolean processMessage(Message msg) {
if (DBG) log("NrConnectedState: process " + getEventName(msg.what));
updateTimers();
+ AsyncResult ar;
switch (msg.what) {
case EVENT_SERVICE_STATE_CHANGED:
- mServiceState = mPhone.getServiceStateTracker().getServiceState();
- if (DBG) log("ServiceState updated: " + mServiceState);
+ onServiceStateChanged();
// fallthrough
case EVENT_UPDATE:
int rat = getDataNetworkType();
@@ -1072,7 +1108,8 @@ public class NetworkTypeController extends StateMachine {
}
break;
case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED:
- updatePhysicalChannelConfigs();
+ ar = (AsyncResult) msg.obj;
+ updatePhysicalChannelConfigs((List<PhysicalChannelConfig>) ar.result);
if (isUsingPhysicalChannelConfigForRrcDetection()) {
mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
}
@@ -1084,8 +1121,7 @@ public class NetworkTypeController extends StateMachine {
}
break;
case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
- AsyncResult ar = (AsyncResult) msg.obj;
- mPhysicalLinkStatus = (int) ar.result;
+ mPhysicalLinkStatus = msg.arg1;
if (!isPhysicalLinkActive() && mFeatureFlags.supportNrSaRrcIdle()) {
transitionWithTimerTo(mNrIdleState);
}
@@ -1124,12 +1160,16 @@ public class NetworkTypeController extends StateMachine {
@Override
public boolean processMessage(Message msg) {
- if (DBG) log("NrConnectedAdvancedState: process " + getEventName(msg.what));
+ mLastShownNrDueToAdvancedBand = isAdditionalNrAdvancedBand(mRatchetedNrBands);
+ if (DBG) {
+ log("NrConnectedAdvancedState: process " + getEventName(msg.what)
+ + ", been using advanced band is " + mLastShownNrDueToAdvancedBand);
+ }
updateTimers();
+ AsyncResult ar;
switch (msg.what) {
case EVENT_SERVICE_STATE_CHANGED:
- mServiceState = mPhone.getServiceStateTracker().getServiceState();
- if (DBG) log("ServiceState updated: " + mServiceState);
+ onServiceStateChanged();
// fallthrough
case EVENT_UPDATE:
int rat = getDataNetworkType();
@@ -1158,7 +1198,8 @@ public class NetworkTypeController extends StateMachine {
}
break;
case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED:
- updatePhysicalChannelConfigs();
+ ar = (AsyncResult) msg.obj;
+ updatePhysicalChannelConfigs((List<PhysicalChannelConfig>) ar.result);
if (isUsingPhysicalChannelConfigForRrcDetection()) {
mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
}
@@ -1170,8 +1211,7 @@ public class NetworkTypeController extends StateMachine {
}
break;
case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
- AsyncResult ar = (AsyncResult) msg.obj;
- mPhysicalLinkStatus = (int) ar.result;
+ mPhysicalLinkStatus = msg.arg1;
break;
default:
return NOT_HANDLED;
@@ -1191,9 +1231,19 @@ public class NetworkTypeController extends StateMachine {
private final NrConnectedAdvancedState mNrConnectedAdvancedState =
new NrConnectedAdvancedState();
- private void updatePhysicalChannelConfigs() {
- List<PhysicalChannelConfig> physicalChannelConfigs =
- mPhone.getServiceStateTracker().getPhysicalChannelConfigList();
+ /** On service state changed. */
+ private void onServiceStateChanged() {
+ ServiceState ss = mPhone.getServiceStateTracker().getServiceState();
+ if (mIsTimerResetEnabledOnPlmnChanges
+ && !TextUtils.equals(mServiceState.getOperatorNumeric(), ss.getOperatorNumeric())) {
+ log("Reset any timers due to nr_timers_reset_on_plmn_change_bool");
+ resetAllTimers();
+ }
+ mServiceState = ss;
+ if (DBG) log("ServiceState updated: " + mServiceState);
+ }
+
+ private void updatePhysicalChannelConfigs(List<PhysicalChannelConfig> physicalChannelConfigs) {
boolean isPccListEmpty = physicalChannelConfigs == null || physicalChannelConfigs.isEmpty();
if (isPccListEmpty && isUsingPhysicalChannelConfigForRrcDetection()) {
log("Physical channel configs updated: not updating PCC fields for empty PCC list "
@@ -1249,7 +1299,7 @@ public class NetworkTypeController extends StateMachine {
} else {
if (mFeatureFlags.supportNrSaRrcIdle() && mDoesPccListIndicateIdle
&& isUsingPhysicalChannelConfigForRrcDetection()
- && !mPrimaryCellChangedWhileIdle && isTimerActiveForNrSaRrcIdle()
+ && !mPrimaryCellChangedWhileIdle && isTimerActiveForRrcIdle()
&& !isNrAdvancedForPccFields(nrBandwidths, nrBands)) {
log("Allow primary cell change during RRC idle timer without changing state: "
+ mLastAnchorNrCellId + " -> " + anchorNrCellId);
@@ -1299,6 +1349,10 @@ public class NetworkTypeController extends StateMachine {
}
if (!mIsDeviceIdleMode && rule != null && rule.getSecondaryTimer(currentName) > 0) {
int duration = rule.getSecondaryTimer(currentName);
+ if (mLastShownNrDueToAdvancedBand && mNrAdvancedBandsSecondaryTimer > 0) {
+ duration = mNrAdvancedBandsSecondaryTimer;
+ if (DBG) log("timer adjusted by nr_advanced_bands_secondary_timer_seconds_int");
+ }
if (DBG) log(duration + "s secondary timer started for state: " + currentName);
mSecondaryTimerState = currentName;
mPreviousState = currentName;
@@ -1349,7 +1403,9 @@ public class NetworkTypeController extends StateMachine {
String currentState = getCurrentState().getName();
- if (mIsPrimaryTimerActive && getOverrideNetworkType() == getCurrentOverrideNetworkType()) {
+ if (mIsPrimaryTimerActive && getOverrideNetworkType() == getCurrentOverrideNetworkType()
+ && getDataNetworkType()
+ == mDisplayInfoController.getTelephonyDisplayInfo().getNetworkType()) {
// remove primary timer if device goes back to the original icon
if (DBG) {
log("Remove primary timer since icon of primary state and current icon equal: "
@@ -1401,13 +1457,17 @@ public class NetworkTypeController extends StateMachine {
mIsSecondaryTimerActive = false;
mPrimaryTimerState = "";
mSecondaryTimerState = "";
+
+ mLastShownNrDueToAdvancedBand = false;
}
- private boolean isTimerActiveForNrSaRrcIdle() {
+ private boolean isTimerActiveForRrcIdle() {
if (mIsPrimaryTimerActive) {
- return mPrimaryTimerState.equals(STATE_CONNECTED_RRC_IDLE);
+ return mPrimaryTimerState.equals(STATE_CONNECTED_RRC_IDLE)
+ || mPrimaryTimerState.equals(STATE_NOT_RESTRICTED_RRC_IDLE);
} else if (mIsSecondaryTimerActive) {
- return mSecondaryTimerState.equals(STATE_CONNECTED_RRC_IDLE);
+ return mSecondaryTimerState.equals(STATE_CONNECTED_RRC_IDLE)
+ || mSecondaryTimerState.equals(STATE_NOT_RESTRICTED_RRC_IDLE);
} else {
return false;
}
diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java
index 3b47670ad1..97eb4475e1 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -5214,6 +5214,12 @@ public abstract class Phone extends Handler implements PhoneInternalInterface {
}
/**
+ * Refresh the safety sources in response to the identified broadcast.
+ */
+ public void refreshSafetySources(String refreshBroadcastId) {
+ }
+
+ /**
* Notifies the IMS call status to the modem.
*
* @param imsCallInfo The list of {@link ImsCallInfo}.
diff --git a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
index 1e0aa3a9f3..7141f379fd 100644
--- a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
+++ b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
@@ -34,6 +34,7 @@ import android.sysprop.TelephonyProperties;
import android.telephony.PhoneCapability;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.telephony.TelephonyRegistryManager;
import android.text.TextUtils;
import android.util.Log;
@@ -46,7 +47,10 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.NoSuchElementException;
+import java.util.Optional;
import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
/**
* This class manages phone's configuration which defines the potential capability (static) of the
@@ -74,7 +78,10 @@ public class PhoneConfigurationManager {
private static PhoneConfigurationManager sInstance = null;
private final Context mContext;
private PhoneCapability mStaticCapability;
- private Set<Integer> mSlotsSupportingSimultaneousCellularCalls = new HashSet<>();
+ private final Set<Integer> mSlotsSupportingSimultaneousCellularCalls = new HashSet<>(3);
+ private final Set<Integer> mSubIdsSupportingSimultaneousCellularCalls = new HashSet<>(3);
+ private final HashSet<Consumer<Set<Integer>>> mSimultaneousCellularCallingListeners =
+ new HashSet<>(1);
private final RadioConfig mRadioConfig;
private final Handler mHandler;
// mPhones is obtained from PhoneFactory and can have phones corresponding to inactive modems as
@@ -148,6 +155,25 @@ public class PhoneConfigurationManager {
}
}
+ /**
+ * Updates the mapping between the slot IDs that support simultaneous calling and the
+ * associated sub IDs as well as notifies listeners.
+ */
+ private void updateSimultaneousSubIdsFromPhoneIdMappingAndNotify() {
+ if (!mFeatureFlags.simultaneousCallingIndications()) return;
+ Set<Integer> slotCandidates = mSlotsSupportingSimultaneousCellularCalls.stream()
+ .map(i -> mPhones[i].getSubId())
+ .filter(i ->i > SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ .collect(Collectors.toSet());
+ if (mSubIdsSupportingSimultaneousCellularCalls.equals(slotCandidates)) return;
+ log("updateSimultaneousSubIdsFromPhoneIdMapping update: "
+ + mSubIdsSupportingSimultaneousCellularCalls + " -> " + slotCandidates);
+ mSubIdsSupportingSimultaneousCellularCalls.clear();
+ mSubIdsSupportingSimultaneousCellularCalls.addAll(slotCandidates);
+ mNotifier.notifySimultaneousCellularCallingSubscriptionsChanged(
+ mSubIdsSupportingSimultaneousCellularCalls);
+ }
+
private void registerForRadioState(Phone phone) {
phone.mCi.registerForAvailable(mHandler, Phone.EVENT_RADIO_AVAILABLE, phone);
}
@@ -163,12 +189,16 @@ public class PhoneConfigurationManager {
/**
* If virtual DSDA is enabled for this UE, then increase maxActiveVoiceSubscriptions to 2.
*/
- private PhoneCapability maybeUpdateMaxActiveVoiceSubscriptions(
+ private PhoneCapability maybeOverrideMaxActiveVoiceSubscriptions(
final PhoneCapability staticCapability) {
- if (staticCapability.getLogicalModemList().size() > 1 && mVirtualDsdaEnabled) {
+ boolean isVDsdaEnabled = staticCapability.getLogicalModemList().size() > 1
+ && mVirtualDsdaEnabled;
+ boolean isBkwdCompatDsdaEnabled = mFeatureFlags.simultaneousCallingIndications()
+ && mMi.getMultiSimProperty().orElse(SSSS).equals(DSDA);
+ if (isVDsdaEnabled || isBkwdCompatDsdaEnabled) {
// Since we already initialized maxActiveVoiceSubscriptions to the count the
- // modem is capable of, vDSDA is only able to increase that count via this method. We do
- // not allow vDSDA to decrease maxActiveVoiceSubscriptions:
+ // modem is capable of, we are only able to increase that count via this method. We do
+ // not allow a decrease of maxActiveVoiceSubscriptions:
int updatedMaxActiveVoiceSubscriptions =
Math.max(staticCapability.getMaxActiveVoiceSubscriptions(), 2);
return new PhoneCapability.Builder(staticCapability)
@@ -180,13 +210,39 @@ public class PhoneConfigurationManager {
}
private void maybeEnableCellularDSDASupport() {
- if (mRadioConfig != null && mRadioConfig.getRadioConfigProxy(null)
- .getVersion().greaterOrEqual(RIL.RADIO_HAL_VERSION_2_2) &&
- getPhoneCount() > 1 &&
- mStaticCapability.getMaxActiveVoiceSubscriptions() > 1) {
+ boolean bkwdsCompatDsda = mFeatureFlags.simultaneousCallingIndications()
+ && getPhoneCount() > 1
+ && mMi.getMultiSimProperty().orElse(SSSS).equals(DSDA);
+ boolean halSupportSimulCalling = mRadioConfig != null
+ && mRadioConfig.getRadioConfigProxy(null).getVersion().greaterOrEqual(
+ RIL.RADIO_HAL_VERSION_2_2)
+ && getPhoneCount() > 1 && mStaticCapability.getMaxActiveVoiceSubscriptions() > 1;
+ // Register for simultaneous calling support changes in the modem if the HAL supports it
+ if (halSupportSimulCalling) {
updateSimultaneousCallingSupport();
mRadioConfig.registerForSimultaneousCallingSupportStatusChanged(mHandler,
EVENT_SIMULTANEOUS_CALLING_SUPPORT_CHANGED, null);
+ } else if (bkwdsCompatDsda) {
+ // For older devices that only declare that they support DSDA via modem config,
+ // set DSDA as capable now statically.
+ log("DSDA modem config detected - setting DSDA enabled");
+ for (Phone p : mPhones) {
+ mSlotsSupportingSimultaneousCellularCalls.add(p.getPhoneId());
+ }
+ updateSimultaneousSubIdsFromPhoneIdMappingAndNotify();
+ notifySimultaneousCellularCallingSlotsChanged();
+ }
+ // Register for subId updates to notify listeners when simultaneous calling is configured
+ if (mFeatureFlags.simultaneousCallingIndications()
+ && (bkwdsCompatDsda || halSupportSimulCalling)) {
+ mContext.getSystemService(TelephonyRegistryManager.class)
+ .addOnSubscriptionsChangedListener(
+ new SubscriptionManager.OnSubscriptionsChangedListener() {
+ @Override
+ public void onSubscriptionsChanged() {
+ updateSimultaneousSubIdsFromPhoneIdMappingAndNotify();
+ }
+ }, mHandler::post);
}
}
@@ -291,7 +347,6 @@ public class PhoneConfigurationManager {
if (mSlotsSupportingSimultaneousCellularCalls.size() > getPhoneCount()) {
loge("Invalid size of DSDA slots. Disabling cellular DSDA.");
mSlotsSupportingSimultaneousCellularCalls.clear();
- break;
}
} else {
log(msg.what + " failure. Not getting logical slots that support "
@@ -299,8 +354,8 @@ public class PhoneConfigurationManager {
mSlotsSupportingSimultaneousCellularCalls.clear();
}
if (mFeatureFlags.simultaneousCallingIndications()) {
- mNotifier.notifySimultaneousCellularCallingSubscriptionsChanged(
- mSlotsSupportingSimultaneousCellularCalls);
+ updateSimultaneousSubIdsFromPhoneIdMappingAndNotify();
+ notifySimultaneousCellularCallingSlotsChanged();
}
break;
default:
@@ -439,7 +494,7 @@ public class PhoneConfigurationManager {
mHandler, EVENT_GET_PHONE_CAPABILITY_DONE);
mRadioConfig.getPhoneCapability(callback);
}
- mStaticCapability = maybeUpdateMaxActiveVoiceSubscriptions(mStaticCapability);
+ mStaticCapability = maybeOverrideMaxActiveVoiceSubscriptions(mStaticCapability);
log("getStaticPhoneCapability: mStaticCapability " + mStaticCapability);
return mStaticCapability;
}
@@ -455,8 +510,31 @@ public class PhoneConfigurationManager {
return mStaticCapability.getMaxActiveDataSubscriptions();
}
+ /**
+ * Register to listen to changes in the Phone slots that support simultaneous calling.
+ * @param consumer A consumer that will be used to consume the new slots supporting simultaneous
+ * cellular calling when it changes.
+ */
+ public void registerForSimultaneousCellularCallingSlotsChanged(
+ Consumer<Set<Integer>> consumer) {
+ mSimultaneousCellularCallingListeners.add(consumer);
+ }
+
+ private void notifySimultaneousCellularCallingSlotsChanged() {
+ log("notifying listeners of changes to simultaneous cellular calling - new state:"
+ + mSlotsSupportingSimultaneousCellularCalls);
+ for (Consumer<Set<Integer>> consumer : mSimultaneousCellularCallingListeners) {
+ try {
+ consumer.accept(new HashSet<>(mSlotsSupportingSimultaneousCellularCalls));
+ } catch (Exception e) {
+ log("Unexpected Exception encountered when notifying listener: " + e);
+ }
+ }
+ }
+
private void notifyCapabilityChanged() {
- mNotifier.notifyPhoneCapabilityChanged(mStaticCapability);
+ mNotifier.notifyPhoneCapabilityChanged(maybeOverrideMaxActiveVoiceSubscriptions(
+ mStaticCapability));
}
/**
@@ -540,6 +618,10 @@ public class PhoneConfigurationManager {
} else {
// The number of active modems is 0 or 1, disable cellular DSDA:
mSlotsSupportingSimultaneousCellularCalls.clear();
+ if (mFeatureFlags.simultaneousCallingIndications()) {
+ updateSimultaneousSubIdsFromPhoneIdMappingAndNotify();
+ notifySimultaneousCellularCallingSlotsChanged();
+ }
}
// When the user enables DSDS mode, the default VOICE and SMS subId should be switched
@@ -717,6 +799,13 @@ public class PhoneConfigurationManager {
Context context, int numOfActiveModems) {
PhoneFactory.onMultiSimConfigChanged(context, numOfActiveModems);
}
+
+ /**
+ * Wrapper function to query the sysprop for multi_sim_config
+ */
+ public Optional<String> getMultiSimProperty() {
+ return TelephonyProperties.multi_sim_config();
+ }
}
private static void log(String s) {
diff --git a/src/java/com/android/internal/telephony/RILUtils.java b/src/java/com/android/internal/telephony/RILUtils.java
index 06176561a0..8897db4e45 100644
--- a/src/java/com/android/internal/telephony/RILUtils.java
+++ b/src/java/com/android/internal/telephony/RILUtils.java
@@ -305,10 +305,10 @@ import android.net.LinkAddress;
import android.net.LinkProperties;
import android.os.SystemClock;
import android.service.carrier.CarrierIdentifier;
-import android.telephony.CarrierInfo;
import android.telephony.AccessNetworkConstants;
import android.telephony.Annotation;
import android.telephony.BarringInfo;
+import android.telephony.CarrierInfo;
import android.telephony.CarrierRestrictionRules;
import android.telephony.CellConfigLte;
import android.telephony.CellIdentity;
@@ -335,7 +335,7 @@ import android.telephony.CellSignalStrengthWcdma;
import android.telephony.CellularIdentifierDisclosure;
import android.telephony.ClosedSubscriberGroupInfo;
import android.telephony.DomainSelectionService;
-import android.telephony.EmergencyRegResult;
+import android.telephony.EmergencyRegistrationResult;
import android.telephony.LinkCapacityEstimate;
import android.telephony.ModemInfo;
import android.telephony.NetworkRegistrationInfo;
@@ -4546,13 +4546,13 @@ public class RILUtils {
}
/**
- * Convert EmergencyRegResult.aidl to EmergencyRegResult.
+ * Convert EmergencyRegResult.aidl to EmergencyRegistrationResult.
* @param halResult EmergencyRegResult.aidl in HAL.
- * @return Converted EmergencyRegResult.
+ * @return Converted EmergencyRegistrationResult.
*/
- public static EmergencyRegResult convertHalEmergencyRegResult(
+ public static EmergencyRegistrationResult convertHalEmergencyRegResult(
android.hardware.radio.network.EmergencyRegResult halResult) {
- return new EmergencyRegResult(
+ return new EmergencyRegistrationResult(
halResult.accessNetwork,
convertHalRegState(halResult.regState),
halResult.emcDomain,
diff --git a/src/java/com/android/internal/telephony/SMSDispatcher.java b/src/java/com/android/internal/telephony/SMSDispatcher.java
index 54c27c5be7..04f5c083ba 100644
--- a/src/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/SMSDispatcher.java
@@ -2544,7 +2544,13 @@ public abstract class SMSDispatcher extends Handler {
/** Return if the SMS was originated from the default SMS application. */
public boolean isFromDefaultSmsApplication(Context context) {
if (mIsFromDefaultSmsApplication == null) {
- UserHandle userHandle = TelephonyUtils.getSubscriptionUserHandle(context, mSubId);
+ UserHandle userHandle;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ userHandle = TelephonyUtils.getSubscriptionUserHandle(context, mSubId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
// Perform a lazy initialization, due to the cost of the operation.
mIsFromDefaultSmsApplication = SmsApplication.isDefaultSmsApplicationAsUser(context,
getAppPackageName(), userHandle);
diff --git a/src/java/com/android/internal/telephony/SimResponse.java b/src/java/com/android/internal/telephony/SimResponse.java
index 164ec7d16b..59defc34f5 100644
--- a/src/java/com/android/internal/telephony/SimResponse.java
+++ b/src/java/com/android/internal/telephony/SimResponse.java
@@ -112,6 +112,8 @@ public class SimResponse extends IRadioSimResponse.Stub {
android.hardware.radio.sim.CarrierRestrictions carrierRestrictions,
int multiSimPolicy) {
RILRequest rr = mRil.processResponse(HAL_SERVICE_SIM, responseInfo);
+ boolean carrierLockInfoSupported = mRil.getHalVersion(HAL_SERVICE_SIM).greater(
+ RIL.RADIO_HAL_VERSION_2_2);
if (rr == null) {
return;
}
@@ -132,7 +134,8 @@ public class SimResponse extends IRadioSimResponse.Stub {
RILUtils.convertAidlCarrierInfoList(
carrierRestrictions.allowedCarrierInfoList)).setExcludedCarrierInfo(
RILUtils.convertAidlCarrierInfoList(
- carrierRestrictions.excludedCarrierInfoList)).build();
+ carrierRestrictions.excludedCarrierInfoList)).setCarrierLockInfoFeature(
+ carrierLockInfoSupported).build();
if (responseInfo.error == RadioError.NONE) {
RadioResponse.sendMessageResponse(rr.mResult, ret);
}
diff --git a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
index 8b41f6e9e7..f5aa0746c5 100644
--- a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
+++ b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
@@ -48,6 +48,7 @@ import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
import com.android.internal.telephony.nitz.NitzStateMachineImpl;
import com.android.internal.telephony.security.CellularIdentifierDisclosureNotifier;
+import com.android.internal.telephony.security.CellularNetworkSecuritySafetySource;
import com.android.internal.telephony.security.NullCipherNotifier;
import com.android.internal.telephony.uicc.IccCardStatus;
import com.android.internal.telephony.uicc.UiccCard;
@@ -278,8 +279,14 @@ public class TelephonyComponentFactory {
return sInstance;
}
- public GsmCdmaCallTracker makeGsmCdmaCallTracker(GsmCdmaPhone phone) {
- return new GsmCdmaCallTracker(phone);
+ /**
+ * Create a new GsmCdmaCallTracker
+ * @param phone GsmCdmaPhone
+ * @param featureFlags Telephony feature flag
+ */
+ public GsmCdmaCallTracker makeGsmCdmaCallTracker(GsmCdmaPhone phone,
+ @NonNull FeatureFlags featureFlags) {
+ return new GsmCdmaCallTracker(phone, featureFlags);
}
public SmsStorageMonitor makeSmsStorageMonitor(Phone phone) {
@@ -569,9 +576,16 @@ public class TelephonyComponentFactory {
return new DataSettingsManager(phone, dataNetworkController, looper, callback);
}
+ /** Create CellularNetworkSecuritySafetySource. */
+ public CellularNetworkSecuritySafetySource makeCellularNetworkSecuritySafetySource(
+ Context context) {
+ return CellularNetworkSecuritySafetySource.getInstance(context);
+ }
+
/** Create CellularIdentifierDisclosureNotifier. */
- public CellularIdentifierDisclosureNotifier makeIdentifierDisclosureNotifier() {
- return CellularIdentifierDisclosureNotifier.getInstance();
+ public CellularIdentifierDisclosureNotifier makeIdentifierDisclosureNotifier(
+ CellularNetworkSecuritySafetySource safetySource) {
+ return CellularIdentifierDisclosureNotifier.getInstance(safetySource);
}
/** Create NullCipherNotifier. */
diff --git a/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java b/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java
index 8cffe7208c..02c459a2b2 100644
--- a/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java
+++ b/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java
@@ -23,6 +23,7 @@ import static android.telephony.SubscriptionManager.INVALID_PHONE_INDEX;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -34,6 +35,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.SystemClock;
import android.provider.Settings;
import android.telephony.AccessNetworkConstants;
import android.telephony.NetworkRegistrationInfo;
@@ -41,7 +43,6 @@ import android.telephony.NetworkRegistrationInfo.RegistrationState;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
import android.telephony.TelephonyDisplayInfo;
import android.util.IndentingPrintWriter;
import android.util.LocalLog;
@@ -61,7 +62,10 @@ import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
@@ -107,7 +111,7 @@ public class AutoDataSwitchController extends Handler {
/** Event for signal strength changed. */
private static final int EVENT_SIGNAL_STRENGTH_CHANGED = 4;
/** Event indicates the switch state is stable, proceed to validation as the next step. */
- private static final int EVENT_MEETS_AUTO_DATA_SWITCH_STATE = 5;
+ private static final int EVENT_STABILITY_CHECK_PASSED = 5;
/** Event when subscriptions changed. */
private static final int EVENT_SUBSCRIPTIONS_CHANGED = 6;
@@ -126,22 +130,54 @@ public class AutoDataSwitchController extends Handler {
/** Notification ID **/
private static final int AUTO_DATA_SWITCH_NOTIFICATION_ID = 1;
+ /**
+ * The threshold of long timer, longer than or equal to which we use alarm manager to schedule
+ * instead of handler.
+ */
+ private static final long RETRY_LONG_DELAY_TIMER_THRESHOLD_MILLIS = TimeUnit
+ .MINUTES.toMillis(1);
+
private final @NonNull LocalLog mLocalLog = new LocalLog(128);
private final @NonNull Context mContext;
private static @NonNull FeatureFlags sFeatureFlags = new FeatureFlagsImpl();
private final @NonNull SubscriptionManagerService mSubscriptionManagerService;
private final @NonNull PhoneSwitcher mPhoneSwitcher;
private final @NonNull AutoDataSwitchControllerCallback mPhoneSwitcherCallback;
+ private final @NonNull AlarmManager mAlarmManager;
+ /** A map of a scheduled event to its associated extra for action when the event fires off. */
+ private final @NonNull Map<Integer, Object> mScheduledEventsToExtras;
+ /** A map of an event to its associated alarm listener callback for when the event fires off. */
+ private final @NonNull Map<Integer, AlarmManager.OnAlarmListener> mEventsToAlarmListener;
+ /**
+ * Event extras for checking environment stability.
+ * @param targetPhoneId The target phone Id to switch to when the stability check pass.
+ * @param isForPerformance Whether the switch is due to RAT/signal strength performance.
+ * @param needValidation Whether ping test needs to pass.
+ */
+ private record StabilityEventExtra(int targetPhoneId, boolean isForPerformance,
+ boolean needValidation) {}
+
+ /**
+ * Event extras for evaluating switch environment.
+ * @param evaluateReason The reason that triggers the evaluation.
+ */
+ private record EvaluateEventExtra(@AutoDataSwitchEvaluationReason int evaluateReason) {}
private boolean mDefaultNetworkIsOnNonCellular = false;
/** {@code true} if we've displayed the notification the first time auto switch occurs **/
private boolean mDisplayedNotification = false;
/**
- * Time threshold in ms to define a internet connection status to be stable(e.g. out of service,
- * in service, wifi is the default active network.etc), while -1 indicates auto switch
- * feature disabled.
+ * Configurable time threshold in ms to define an internet connection status to be stable(e.g.
+ * out of service, in service, wifi is the default active network.etc), while -1 indicates auto
+ * switch feature disabled.
*/
private long mAutoDataSwitchAvailabilityStabilityTimeThreshold = -1;
/**
+ * Configurable time threshold in ms to define an internet connection performance status to be
+ * stable (e.g. LTE + 4 signal strength, UMTS + 2 signal strength), while -1 indicates
+ * auto switch feature based on RAT/SS is disabled.
+ */
+ private long mAutoDataSwitchPerformanceStabilityTimeThreshold = -1;
+ /**
* The tolerated gap of score for auto data switch decision, larger than which the device will
* switch to the SIM with higher score. If 0, the device will always switch to the higher score
* SIM. If < 0, the network type and signal strength based auto switch is disabled.
@@ -250,8 +286,8 @@ public class AutoDataSwitchController extends Handler {
return "{phone " + mPhone.getPhoneId()
+ " score=" + getRatSignalScore() + " dataRegState="
+ NetworkRegistrationInfo.registrationStateToString(mDataRegState)
- + " " + getUsableState()
- + " display=" + mDisplayInfo + " signalStrength=" + mSignalStrength.getLevel()
+ + " " + getUsableState() + " " + mDisplayInfo
+ + " signalStrength=" + mSignalStrength.getLevel()
+ " listeningForEvents=" + mListeningForEvents
+ "}";
@@ -296,9 +332,12 @@ public class AutoDataSwitchController extends Handler {
super(looper);
mContext = context;
sFeatureFlags = featureFlags;
+ mPhoneSwitcherCallback = phoneSwitcherCallback;
+ mAlarmManager = context.getSystemService(AlarmManager.class);
+ mScheduledEventsToExtras = new HashMap<>();
+ mEventsToAlarmListener = new HashMap<>();
mSubscriptionManagerService = SubscriptionManagerService.getInstance();
mPhoneSwitcher = phoneSwitcher;
- mPhoneSwitcherCallback = phoneSwitcherCallback;
readDeviceResourceConfig();
int numActiveModems = PhoneFactory.getPhones().length;
mPhonesSignalStatus = new PhoneSignalStatus[numActiveModems];
@@ -407,6 +446,8 @@ public class AutoDataSwitchController extends Handler {
mRequirePingTestBeforeSwitch = dataConfig.isPingTestBeforeAutoDataSwitchRequired();
mAutoDataSwitchAvailabilityStabilityTimeThreshold =
dataConfig.getAutoDataSwitchAvailabilityStabilityTimeThreshold();
+ mAutoDataSwitchPerformanceStabilityTimeThreshold =
+ dataConfig.getAutoDataSwitchPerformanceStabilityTimeThreshold();
mAutoDataSwitchValidationMaxRetry =
dataConfig.getAutoDataSwitchValidationMaxRetry();
}
@@ -432,15 +473,35 @@ public class AutoDataSwitchController extends Handler {
onSignalStrengthChanged(phoneId);
break;
case EVENT_EVALUATE_AUTO_SWITCH:
- int reason = (int) msg.obj;
- onEvaluateAutoDataSwitch(reason);
+ if (sFeatureFlags.autoDataSwitchRatSs()) {
+ Object obj = mScheduledEventsToExtras.get(EVENT_EVALUATE_AUTO_SWITCH);
+ if (obj instanceof EvaluateEventExtra extra) {
+ mScheduledEventsToExtras.remove(EVENT_EVALUATE_AUTO_SWITCH);
+ onEvaluateAutoDataSwitch(extra.evaluateReason);
+ }
+ } else {
+ int reason = (int) msg.obj;
+ onEvaluateAutoDataSwitch(reason);
+ }
break;
- case EVENT_MEETS_AUTO_DATA_SWITCH_STATE:
- int targetPhoneId = msg.arg1;
- boolean needValidation = msg.arg2 == 1;
- log("require validation on phone " + targetPhoneId
- + (needValidation ? "" : " no") + " need to pass");
- mPhoneSwitcherCallback.onRequireValidation(targetPhoneId, needValidation);
+ case EVENT_STABILITY_CHECK_PASSED:
+ if (sFeatureFlags.autoDataSwitchRatSs()) {
+ Object obj = mScheduledEventsToExtras.get(EVENT_STABILITY_CHECK_PASSED);
+ if (obj instanceof StabilityEventExtra extra) {
+ int targetPhoneId = extra.targetPhoneId;
+ boolean needValidation = extra.needValidation;
+ log("require validation on phone " + targetPhoneId
+ + (needValidation ? "" : " no") + " need to pass");
+ mScheduledEventsToExtras.remove(EVENT_STABILITY_CHECK_PASSED);
+ mPhoneSwitcherCallback.onRequireValidation(targetPhoneId, needValidation);
+ }
+ } else {
+ int targetPhoneId = msg.arg1;
+ boolean needValidation = msg.arg2 == 1;
+ log("require validation on phone " + targetPhoneId
+ + (needValidation ? "" : " no") + " need to pass");
+ mPhoneSwitcherCallback.onRequireValidation(targetPhoneId, needValidation);
+ }
break;
case EVENT_SUBSCRIPTIONS_CHANGED:
onSubscriptionsChanged();
@@ -533,16 +594,21 @@ public class AutoDataSwitchController extends Handler {
/**
* Called as a preliminary check for the frequent signal/display info change.
- * @return The phone Id if found a candidate phone with higher signal score.
+ * @return The phone Id if found a candidate phone with higher signal score, or the DDS has
+ * an equal score.
*/
private int getHigherScoreCandidatePhoneId() {
int preferredPhoneId = mPhoneSwitcher.getPreferredDataPhoneId();
- if (isActiveModemPhone(preferredPhoneId)) {
+ int ddsPhoneId = mSubscriptionManagerService.getPhoneId(
+ mSubscriptionManagerService.getDefaultDataSubId());
+ if (isActiveModemPhone(preferredPhoneId) && isActiveModemPhone(ddsPhoneId)) {
int currentScore = mPhonesSignalStatus[preferredPhoneId].getRatSignalScore();
for (int phoneId = 0; phoneId < mPhonesSignalStatus.length; phoneId++) {
+ if (phoneId == preferredPhoneId) continue;
int candidateScore = mPhonesSignalStatus[phoneId].getRatSignalScore();
- if (phoneId != preferredPhoneId
- && (candidateScore - currentScore) > mScoreTolerance) {
+ if ((candidateScore - currentScore) > mScoreTolerance
+ // Also reevaluate if DDS has the same score as the current phone.
+ || (candidateScore >= currentScore && phoneId == ddsPhoneId)) {
return phoneId;
}
}
@@ -559,8 +625,15 @@ public class AutoDataSwitchController extends Handler {
? mAutoDataSwitchAvailabilityStabilityTimeThreshold
<< mAutoSwitchValidationFailedCount
: 0;
- if (!hasMessages(EVENT_EVALUATE_AUTO_SWITCH)) {
- sendMessageDelayed(obtainMessage(EVENT_EVALUATE_AUTO_SWITCH, reason), delayMs);
+ if (sFeatureFlags.autoDataSwitchRatSs()) {
+ if (!mScheduledEventsToExtras.containsKey(EVENT_EVALUATE_AUTO_SWITCH)) {
+ scheduleEventWithTimer(EVENT_EVALUATE_AUTO_SWITCH, new EvaluateEventExtra(reason),
+ delayMs);
+ }
+ } else {
+ if (!hasMessages(EVENT_EVALUATE_AUTO_SWITCH)) {
+ sendMessageDelayed(obtainMessage(EVENT_EVALUATE_AUTO_SWITCH, reason), delayMs);
+ }
}
}
@@ -576,14 +649,15 @@ public class AutoDataSwitchController extends Handler {
int defaultDataSubId = mSubscriptionManagerService.getDefaultDataSubId();
// check is valid DSDS
if (mSubscriptionManagerService.getActiveSubIdList(true).length < 2) return;
- Phone defaultDataPhone = PhoneFactory.getPhone(mSubscriptionManagerService.getPhoneId(
- defaultDataSubId));
+ int defaultDataPhoneId = mSubscriptionManagerService.getPhoneId(
+ defaultDataSubId);
+ Phone defaultDataPhone = PhoneFactory.getPhone(defaultDataPhoneId);
if (defaultDataPhone == null) {
loge("onEvaluateAutoDataSwitch: cannot find the phone associated with default data"
+ " subscription " + defaultDataSubId);
return;
}
- int defaultDataPhoneId = defaultDataPhone.getPhoneId();
+
int preferredPhoneId = mPhoneSwitcher.getPreferredDataPhoneId();
StringBuilder debugMessage = new StringBuilder("onEvaluateAutoDataSwitch:");
debugMessage.append(" defaultPhoneId: ").append(defaultDataPhoneId)
@@ -591,11 +665,11 @@ public class AutoDataSwitchController extends Handler {
.append(", reason: ").append(evaluationReasonToString(reason));
if (preferredPhoneId == defaultDataPhoneId) {
// on default data sub
- int candidatePhoneId = getSwitchCandidatePhoneId(defaultDataPhoneId, debugMessage);
+ StabilityEventExtra res = evaluateAnyCandidateToUse(defaultDataPhoneId, debugMessage);
log(debugMessage.toString());
- if (candidatePhoneId != INVALID_PHONE_INDEX) {
- mSelectedTargetPhoneId = candidatePhoneId;
- startStabilityCheck(candidatePhoneId, mRequirePingTestBeforeSwitch);
+ if (res.targetPhoneId != INVALID_PHONE_INDEX) {
+ mSelectedTargetPhoneId = res.targetPhoneId;
+ startStabilityCheck(res.targetPhoneId, res.isForPerformance, res.needValidation);
} else {
cancelAnyPendingSwitch();
}
@@ -617,6 +691,7 @@ public class AutoDataSwitchController extends Handler {
}
boolean backToDefault = false;
+ boolean isForPerformance = false;
boolean needValidation = true;
if (sFeatureFlags.autoSwitchAllowRoaming()) {
@@ -654,12 +729,13 @@ public class AutoDataSwitchController extends Handler {
.getRatSignalScore();
int currentScore = mPhonesSignalStatus[preferredPhoneId]
.getRatSignalScore();
- if ((defaultScore - currentScore) > mScoreTolerance) {
+ if (defaultScore >= currentScore) {
debugMessage
- .append(", back to default for higher score ")
+ .append(", back to default for higher or equal score ")
.append(defaultScore).append(" versus current ")
.append(currentScore);
backToDefault = true;
+ isForPerformance = true;
needValidation = mRequirePingTestBeforeSwitch;
}
} else {
@@ -688,12 +764,13 @@ public class AutoDataSwitchController extends Handler {
} else if (isRatSignalStrengthBasedSwitchEnabled()) {
int defaultScore = mPhonesSignalStatus[defaultDataPhoneId].getRatSignalScore();
int currentScore = mPhonesSignalStatus[preferredPhoneId].getRatSignalScore();
- if ((defaultScore - currentScore) > mScoreTolerance) {
+ if (defaultScore >= currentScore) {
debugMessage
- .append(", back to default as default phone has higher score ")
+ .append(", back to default as default has higher or equal score ")
.append(defaultScore).append(" versus current ")
.append(currentScore);
backToDefault = true;
+ isForPerformance = true;
needValidation = mRequirePingTestBeforeSwitch;
}
} else if (isInService(mPhonesSignalStatus[defaultDataPhoneId].mDataRegState)) {
@@ -706,7 +783,7 @@ public class AutoDataSwitchController extends Handler {
if (backToDefault) {
log(debugMessage.toString());
mSelectedTargetPhoneId = defaultDataPhoneId;
- startStabilityCheck(DEFAULT_PHONE_INDEX, needValidation);
+ startStabilityCheck(DEFAULT_PHONE_INDEX, isForPerformance, needValidation);
} else {
// cancel any previous attempts of switching back to default phone
cancelAnyPendingSwitch();
@@ -718,25 +795,29 @@ public class AutoDataSwitchController extends Handler {
* Called when consider switching from primary default data sub to another data sub.
* @param defaultPhoneId The default data phone
* @param debugMessage Debug message.
- * @return the target subId if a suitable candidate is found, otherwise return
- * {@link SubscriptionManager#INVALID_PHONE_INDEX}
+ * @return StabilityEventExtra As evaluation result.
*/
- private int getSwitchCandidatePhoneId(int defaultPhoneId, @NonNull StringBuilder debugMessage) {
+ @NonNull private StabilityEventExtra evaluateAnyCandidateToUse(int defaultPhoneId,
+ @NonNull StringBuilder debugMessage) {
Phone defaultDataPhone = PhoneFactory.getPhone(defaultPhoneId);
+ boolean isForPerformance = false;
+ StabilityEventExtra invalidResult = new StabilityEventExtra(INVALID_PHONE_INDEX,
+ isForPerformance, mRequirePingTestBeforeSwitch);
+
if (defaultDataPhone == null) {
debugMessage.append(", no candidate as no sim loaded");
- return INVALID_PHONE_INDEX;
+ return invalidResult;
}
if (!defaultDataPhone.isUserDataEnabled()) {
debugMessage.append(", no candidate as user disabled mobile data");
- return INVALID_PHONE_INDEX;
+ return invalidResult;
}
if (mDefaultNetworkIsOnNonCellular) {
debugMessage.append(", no candidate as default network is active")
.append(" on non-cellular transport");
- return INVALID_PHONE_INDEX;
+ return invalidResult;
}
if (sFeatureFlags.autoSwitchAllowRoaming()) {
@@ -744,14 +825,14 @@ public class AutoDataSwitchController extends Handler {
if (!isRatSignalStrengthBasedSwitchEnabled()
&& isHomeService(mPhonesSignalStatus[defaultPhoneId].mDataRegState)) {
debugMessage.append(", no candidate as default phone is in HOME service");
- return INVALID_PHONE_INDEX;
+ return invalidResult;
}
} else {
// check whether primary and secondary signal status are worth switching
if (!isRatSignalStrengthBasedSwitchEnabled()
&& isInService(mPhonesSignalStatus[defaultPhoneId].mDataRegState)) {
debugMessage.append(", no candidate as default phone is in service");
- return INVALID_PHONE_INDEX;
+ return invalidResult;
}
}
@@ -764,27 +845,27 @@ public class AutoDataSwitchController extends Handler {
if (sFeatureFlags.autoSwitchAllowRoaming()) {
PhoneSignalStatus.UsableState currentUsableState =
mPhonesSignalStatus[defaultPhoneId].getUsableState();
- PhoneSignalStatus.UsableState candidatePhoneUsableRank =
+ PhoneSignalStatus.UsableState candidateUsableState =
mPhonesSignalStatus[phoneId].getUsableState();
- debugMessage.append(", found phone ").append(phoneId).append(" is ").append(
- candidatePhoneUsableRank)
- .append(", current is ").append(currentUsableState);
- if (candidatePhoneUsableRank.mScore > currentUsableState.mScore) {
+ debugMessage.append(", found phone ").append(phoneId).append(" ")
+ .append(candidateUsableState)
+ .append(", default is ").append(currentUsableState);
+ if (candidateUsableState.mScore > currentUsableState.mScore) {
secondaryDataPhone = PhoneFactory.getPhone(phoneId);
} else if (isRatSignalStrengthBasedSwitchEnabled()
- && currentUsableState.mScore == candidatePhoneUsableRank.mScore) {
+ && currentUsableState.mScore == candidateUsableState.mScore) {
// Both phones are home or both roaming enabled, so compare RAT/signal score.
int defaultScore = defaultPhoneStatus.getRatSignalScore();
int candidateScore = candidatePhoneStatus.getRatSignalScore();
if ((candidateScore - defaultScore) > mScoreTolerance) {
- debugMessage.append(" with higher score ").append(
- candidateScore)
- .append(" versus current ").append(defaultScore);
+ debugMessage.append(" with ").append(defaultScore)
+ .append(" versus candidate higher score ").append(candidateScore);
secondaryDataPhone = PhoneFactory.getPhone(phoneId);
+ isForPerformance = true;
} else {
- debugMessage.append(", but its score ").append(candidateScore)
- .append(" doesn't meet the bar to switch given the current ")
+ debugMessage.append(", candidate's score ").append(candidateScore)
+ .append(" doesn't justify the switch given the current ")
.append(defaultScore);
}
}
@@ -802,6 +883,7 @@ public class AutoDataSwitchController extends Handler {
debugMessage.append(" with higher score ").append(candidateScore)
.append(" versus current ").append(defaultScore);
secondaryDataPhone = PhoneFactory.getPhone(phoneId);
+ isForPerformance = true;
} else {
debugMessage.append(", but its score ").append(candidateScore)
.append(" doesn't meet the bar to switch given the current ")
@@ -817,21 +899,23 @@ public class AutoDataSwitchController extends Handler {
if (secondaryDataPhone != null) {
// check auto switch feature enabled
if (secondaryDataPhone.isDataAllowed()) {
- return phoneId;
+ return new StabilityEventExtra(phoneId,
+ isForPerformance, mRequirePingTestBeforeSwitch);
} else {
- debugMessage.append(", but its data is not allowed");
+ debugMessage.append(", but candidate's data is not allowed");
}
}
}
debugMessage.append(", found no qualified candidate.");
- return INVALID_PHONE_INDEX;
+ return invalidResult;
}
/**
* @return {@code true} If the feature of switching base on RAT and signal strength is enabled.
*/
private boolean isRatSignalStrengthBasedSwitchEnabled() {
- return sFeatureFlags.autoDataSwitchRatSs() && mScoreTolerance >= 0;
+ return sFeatureFlags.autoDataSwitchRatSs() && mScoreTolerance >= 0
+ && mAutoDataSwitchPerformanceStabilityTimeThreshold >= 0;
}
/**
@@ -839,18 +923,77 @@ public class AutoDataSwitchController extends Handler {
* Start pre-switch validation if the current environment suits auto data switch for
* {@link #mAutoDataSwitchAvailabilityStabilityTimeThreshold} MS.
* @param targetPhoneId the target phone Id.
+ * @param isForPerformance {@code true} entails longer stability check.
* @param needValidation {@code true} if validation is needed.
*/
- private void startStabilityCheck(int targetPhoneId, boolean needValidation) {
- log("startAutoDataSwitchStabilityCheck: targetPhoneId=" + targetPhoneId
- + " needValidation=" + needValidation);
+ private void startStabilityCheck(int targetPhoneId, boolean isForPerformance,
+ boolean needValidation) {
String combinationIdentifier = targetPhoneId + "" + needValidation;
- if (!hasEqualMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE, combinationIdentifier)) {
- removeMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE);
- sendMessageDelayed(obtainMessage(EVENT_MEETS_AUTO_DATA_SWITCH_STATE, targetPhoneId,
+ if (sFeatureFlags.autoDataSwitchRatSs()) {
+ StabilityEventExtra eventExtras = (StabilityEventExtra)
+ mScheduledEventsToExtras.getOrDefault(EVENT_STABILITY_CHECK_PASSED,
+ new StabilityEventExtra(INVALID_PHONE_INDEX, false /*need validation*/,
+ false /*isForPerformance*/));
+ long delayMs = -1;
+ // Check if already scheduled one with that combination of extras.
+ if (eventExtras.targetPhoneId != targetPhoneId
+ || eventExtras.needValidation != needValidation
+ || eventExtras.isForPerformance != isForPerformance) {
+ eventExtras =
+ new StabilityEventExtra(targetPhoneId, isForPerformance, needValidation);
+
+ // Reset with new timer.
+ delayMs = isForPerformance
+ ? mAutoDataSwitchPerformanceStabilityTimeThreshold
+ : mAutoDataSwitchAvailabilityStabilityTimeThreshold;
+ scheduleEventWithTimer(EVENT_STABILITY_CHECK_PASSED, eventExtras, delayMs);
+ }
+ log("startStabilityCheck: "
+ + (delayMs != -1 ? "scheduling " : "already scheduled ")
+ + eventExtras);
+ } else if (!hasEqualMessages(EVENT_STABILITY_CHECK_PASSED, combinationIdentifier)) {
+ removeMessages(EVENT_STABILITY_CHECK_PASSED);
+ sendMessageDelayed(obtainMessage(EVENT_STABILITY_CHECK_PASSED, targetPhoneId,
needValidation ? 1 : 0,
combinationIdentifier),
mAutoDataSwitchAvailabilityStabilityTimeThreshold);
+ log("startStabilityCheck: targetPhoneId=" + targetPhoneId
+ + " isForPerformance=" + isForPerformance
+ + " needValidation=" + needValidation);
+ }
+ }
+
+ /**
+ * Use when need to schedule with timer. Short timer uses handler, while the longer timer uses
+ * alarm manager to account for real time elapse.
+ *
+ * @param event The event.
+ * @param extras Any extra data associated with the event.
+ * @param delayMs The delayed interval in ms.
+ */
+ private void scheduleEventWithTimer(int event, @NonNull Object extras, long delayMs) {
+ // Get singleton alarm listener.
+ mEventsToAlarmListener.putIfAbsent(event, () -> sendEmptyMessage(event));
+ AlarmManager.OnAlarmListener listener = mEventsToAlarmListener.get(event);
+
+ // Cancel any existing.
+ removeMessages(event);
+ mAlarmManager.cancel(listener);
+ // Override with new extras.
+ mScheduledEventsToExtras.put(event, extras);
+ // Reset timer.
+ if (delayMs <= RETRY_LONG_DELAY_TIMER_THRESHOLD_MILLIS) {
+ // Use handler for short timer.
+ sendEmptyMessageDelayed(event, delayMs);
+ } else {
+ // Not using setWhileIdle because it can wait util the next time the device wakes up to
+ // save power.
+ // If another evaluation is processed before the alarm fires,
+ // this timer is restarted (AlarmManager using the same listener resets the
+ // timer).
+ mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME,
+ SystemClock.elapsedRealtime() + delayMs,
+ LOG_TAG /*debug tag*/, listener, this);
}
}
@@ -906,7 +1049,19 @@ public class AutoDataSwitchController extends Handler {
private void cancelAnyPendingSwitch() {
mSelectedTargetPhoneId = INVALID_PHONE_INDEX;
resetFailedCount();
- removeMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE);
+ if (sFeatureFlags.autoDataSwitchRatSs()) {
+ if (mScheduledEventsToExtras.containsKey(EVENT_STABILITY_CHECK_PASSED)) {
+ if (mEventsToAlarmListener.containsKey(EVENT_STABILITY_CHECK_PASSED)) {
+ mAlarmManager.cancel(mEventsToAlarmListener.get(EVENT_STABILITY_CHECK_PASSED));
+ } else {
+ loge("cancelAnyPendingSwitch: EVENT_STABILITY_CHECK_PASSED listener is null");
+ }
+ removeMessages(EVENT_STABILITY_CHECK_PASSED);
+ mScheduledEventsToExtras.remove(EVENT_STABILITY_CHECK_PASSED);
+ }
+ } else {
+ removeMessages(EVENT_STABILITY_CHECK_PASSED);
+ }
mPhoneSwitcherCallback.onRequireCancelAnyPendingAutoSwitchValidation();
}
@@ -920,8 +1075,6 @@ public class AutoDataSwitchController extends Handler {
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
if (mDisplayedNotification) {
// cancel posted notification if any exist
- log("displayAutoDataSwitchNotification: canceling any notifications for phone "
- + phoneId);
notificationManager.cancel(AUTO_DATA_SWITCH_NOTIFICATION_TAG,
AUTO_DATA_SWITCH_NOTIFICATION_ID);
return;
diff --git a/src/java/com/android/internal/telephony/data/DataConfigManager.java b/src/java/com/android/internal/telephony/data/DataConfigManager.java
index ea7b1da2a8..90743f85d8 100644
--- a/src/java/com/android/internal/telephony/data/DataConfigManager.java
+++ b/src/java/com/android/internal/telephony/data/DataConfigManager.java
@@ -870,6 +870,14 @@ public class DataConfigManager extends Handler {
}
/**
+ * @return the data limit in bytes that can be used for esim bootstrap usage.
+ */
+ public long getEsimBootStrapMaxDataLimitBytes() {
+ return mResources.getInteger(
+ com.android.internal.R.integer.config_esim_bootstrap_data_limit_bytes);
+ }
+
+ /**
* Update the TCP buffer sizes from the resource overlays.
*/
private void updateTcpBuffers() {
@@ -1061,6 +1069,16 @@ public class DataConfigManager extends Handler {
}
/**
+ * @return Time threshold in ms to define a internet connection performance status to be stable
+ * (e.g. LTE + 4 signal strength, UMTS + 2 signal strength), while -1 indicates
+ * auto switch feature based on RAT/SS is disabled.
+ */
+ public long getAutoDataSwitchPerformanceStabilityTimeThreshold() {
+ return mResources.getInteger(com.android.internal.R.integer
+ .auto_data_switch_performance_stability_time_threshold_millis);
+ }
+
+ /**
* Get the TCP config string, used by {@link LinkProperties#setTcpBufferSizes(String)}.
* The config string will have the following form, with values in bytes:
* "read_min,read_default,read_max,write_min,write_default,write_max"
@@ -1398,6 +1416,17 @@ public class DataConfigManager extends Handler {
}
/**
+ * @return Indicating whether the retry timer from setup data call response for data throttling
+ * should be honored for emergency network request. By default this is off, meaning emergency
+ * network requests will ignore the previous retry timer passed in from setup data call
+ * response.
+ */
+ public boolean shouldHonorRetryTimerForEmergencyNetworkRequest() {
+ return mResources.getBoolean(
+ com.android.internal.R.bool.config_honor_data_retry_timer_for_emergency_network);
+ }
+
+ /**
* Log debug messages.
* @param s debug messages
*/
@@ -1440,6 +1469,8 @@ public class DataConfigManager extends Handler {
pw.increaseIndent();
mDataHandoverRetryRules.forEach(pw::println);
pw.decreaseIndent();
+ pw.println("shouldHonorRetryTimerForEmergencyNetworkRequest="
+ + shouldHonorRetryTimerForEmergencyNetworkRequest());
pw.println("mSetupDataCallAnomalyReport=" + mSetupDataCallAnomalyReportThreshold);
pw.println("mNetworkUnwantedAnomalyReport=" + mNetworkUnwantedAnomalyReportThreshold);
pw.println("mImsReleaseRequestAnomalyReport=" + mImsReleaseRequestAnomalyReportThreshold);
@@ -1456,6 +1487,8 @@ public class DataConfigManager extends Handler {
+ Arrays.toString(value)));
pw.println("getAutoDataSwitchAvailabilityStabilityTimeThreshold="
+ getAutoDataSwitchAvailabilityStabilityTimeThreshold());
+ pw.println("getAutoDataSwitchPerformanceStabilityTimeThreshold="
+ + getAutoDataSwitchPerformanceStabilityTimeThreshold());
pw.println("getAutoDataSwitchValidationMaxRetry=" + getAutoDataSwitchValidationMaxRetry());
pw.decreaseIndent();
pw.println("Metered APN types=" + mMeteredApnTypes.stream()
diff --git a/src/java/com/android/internal/telephony/data/DataEvaluation.java b/src/java/com/android/internal/telephony/data/DataEvaluation.java
index 6858c6a032..3d10e9cd22 100644
--- a/src/java/com/android/internal/telephony/data/DataEvaluation.java
+++ b/src/java/com/android/internal/telephony/data/DataEvaluation.java
@@ -257,7 +257,9 @@ public class DataEvaluation {
/** Tracking area code changed. */
TAC_CHANGED(true),
/** Unsatisfied network request detached. */
- UNSATISFIED_REQUEST_DETACHED(true);
+ UNSATISFIED_REQUEST_DETACHED(true),
+ /** track bootstrap sim data usage */
+ CHECK_DATA_USAGE(false);
/**
* {@code true} if the evaluation is due to environmental changes (i.e. SIM removal,
@@ -342,7 +344,9 @@ public class DataEvaluation {
/** Data enabled settings are not ready. */
DATA_SETTINGS_NOT_READY(true),
/** Handover max retry stopped but network is not on the preferred transport. */
- HANDOVER_RETRY_STOPPED(true);
+ HANDOVER_RETRY_STOPPED(true),
+ /** BootStrap sim data limit reached. */
+ DATA_LIMIT_REACHED(true);
private final boolean mIsHardReason;
diff --git a/src/java/com/android/internal/telephony/data/DataNetwork.java b/src/java/com/android/internal/telephony/data/DataNetwork.java
index 6ae861c31c..c1e6155468 100644
--- a/src/java/com/android/internal/telephony/data/DataNetwork.java
+++ b/src/java/com/android/internal/telephony/data/DataNetwork.java
@@ -122,6 +122,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -312,6 +313,7 @@ public class DataNetwork extends StateMachine {
TEAR_DOWN_REASON_ILLEGAL_STATE,
TEAR_DOWN_REASON_ONLY_ALLOWED_SINGLE_NETWORK,
TEAR_DOWN_REASON_PREFERRED_DATA_SWITCHED,
+ TEAR_DOWN_REASON_DATA_LIMIT_REACHED,
})
public @interface TearDownReason {}
@@ -408,6 +410,9 @@ public class DataNetwork extends StateMachine {
/** Data network tear down due to preferred data switched to another phone. */
public static final int TEAR_DOWN_REASON_PREFERRED_DATA_SWITCHED = 30;
+ /** Data network tear down due to bootstrap sim data limit reached. */
+ public static final int TEAR_DOWN_REASON_DATA_LIMIT_REACHED = 31;
+
//********************************************************************************************//
// WHENEVER ADD A NEW TEAR DOWN REASON, PLEASE UPDATE DataDeactivateReasonEnum in enums.proto //
//********************************************************************************************//
@@ -917,6 +922,14 @@ public class DataNetwork extends StateMachine {
*/
public abstract void onRetryUnsatisfiedNetworkRequest(
@NonNull TelephonyNetworkRequest networkRequest);
+
+ /**
+ * Called when QosBearerSessions bearer changed, which indicates VoNr or VoLte calls.
+ *
+ * @param qosBearerSessions The current qosBearerSessions.
+ */
+ public abstract void onQosSessionsChanged(
+ @NonNull List<QosBearerSession> qosBearerSessions);
}
/**
@@ -2672,6 +2685,12 @@ public class DataNetwork extends StateMachine {
mDefaultQos = response.getDefaultQos();
+
+ Set<QosBearerSession> newSessions = new HashSet<>(response.getQosBearerSessions());
+ if (newSessions.size() != mQosBearerSessions.size()
+ || !newSessions.containsAll(mQosBearerSessions)) {
+ mDataNetworkCallback.onQosSessionsChanged(response.getQosBearerSessions());
+ }
mQosBearerSessions.clear();
mQosBearerSessions.addAll(response.getQosBearerSessions());
if (mQosCallbackTracker != null) {
@@ -3757,6 +3776,8 @@ public class DataNetwork extends StateMachine {
return "TEAR_DOWN_REASON_ONLY_ALLOWED_SINGLE_NETWORK";
case TEAR_DOWN_REASON_PREFERRED_DATA_SWITCHED:
return "TEAR_DOWN_REASON_PREFERRED_DATA_SWITCHED";
+ case TEAR_DOWN_REASON_DATA_LIMIT_REACHED:
+ return "TEAR_DOWN_REASON_DATA_LIMIT_REACHED";
default:
return "UNKNOWN(" + reason + ")";
}
diff --git a/src/java/com/android/internal/telephony/data/DataNetworkController.java b/src/java/com/android/internal/telephony/data/DataNetworkController.java
index 645e8bd1a1..3ca28a33ca 100644
--- a/src/java/com/android/internal/telephony/data/DataNetworkController.java
+++ b/src/java/com/android/internal/telephony/data/DataNetworkController.java
@@ -20,6 +20,8 @@ import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.usage.NetworkStats;
+import android.app.usage.NetworkStatsManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -29,6 +31,7 @@ import android.net.NetworkCapabilities;
import android.net.NetworkPolicyManager;
import android.net.NetworkPolicyManager.SubscriptionCallback;
import android.net.NetworkRequest;
+import android.net.NetworkTemplate;
import android.net.Uri;
import android.os.AsyncResult;
import android.os.Handler;
@@ -66,6 +69,7 @@ import android.telephony.data.DataCallResponse.HandoverFailureMode;
import android.telephony.data.DataCallResponse.LinkStatus;
import android.telephony.data.DataProfile;
import android.telephony.data.DataServiceCallback;
+import android.telephony.data.QosBearerSession;
import android.telephony.ims.ImsException;
import android.telephony.ims.ImsManager;
import android.telephony.ims.ImsReasonInfo;
@@ -238,6 +242,18 @@ public class DataNetworkController extends Handler {
private static final long REEVALUATE_UNSATISFIED_NETWORK_REQUESTS_AFTER_DETACHED_DELAY_MILLIS =
TimeUnit.SECONDS.toMillis(1);
+ /**
+ * The delay in milliseconds to re-evaluate existing data networks for bootstrap sim data usage
+ * limit.
+ */
+ private static final long REEVALUATE_BOOTSTRAP_SIM_DATA_USAGE_MILLIS =
+ TimeUnit.SECONDS.toMillis(60);
+
+ /**
+ * bootstrap sim total data usage bytes
+ */
+ private long mBootStrapSimTotalDataUsageBytes = 0L;
+
private final Phone mPhone;
private final String mLogTag;
private final LocalLog mLocalLog = new LocalLog(128);
@@ -632,6 +648,13 @@ public class DataNetworkController extends Handler {
* @param simState The current SIM state
*/
public void onSimStateChanged(@SimState int simState) {}
+
+ /**
+ * Called when QosBearerSessions changed.
+ *
+ * @param qosBearerSessions The latest QOS bearer sessions.
+ */
+ public void onQosSessionsChanged(@NonNull List<QosBearerSession> qosBearerSessions) {}
}
/**
@@ -1505,11 +1528,27 @@ public class DataNetworkController extends Handler {
// Bypass all checks for emergency network request.
if (networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_EIMS)) {
- evaluation.addDataAllowedReason(DataAllowedReason.EMERGENCY_REQUEST);
- evaluation.setCandidateDataProfile(mDataProfileManager.getDataProfileForNetworkRequest(
+ DataProfile emergencyProfile = mDataProfileManager.getDataProfileForNetworkRequest(
networkRequest, getDataNetworkType(transport),
mServiceState.isUsingNonTerrestrialNetwork(),
- isEsimBootStrapProvisioningActivated(), true));
+ isEsimBootStrapProvisioningActivated(), true);
+
+ // Check if the profile is being throttled.
+ if (mDataConfigManager.shouldHonorRetryTimerForEmergencyNetworkRequest()
+ && emergencyProfile != null
+ && mDataRetryManager.isDataProfileThrottled(emergencyProfile, transport)) {
+ evaluation.addDataDisallowedReason(DataDisallowedReason.DATA_THROTTLED);
+ log("Emergency network request is throttled by the previous setup data "
+ + "call response.");
+ log(evaluation.toString());
+ networkRequest.setEvaluation(evaluation);
+ return evaluation;
+ }
+
+ evaluation.addDataAllowedReason(DataAllowedReason.EMERGENCY_REQUEST);
+ if (emergencyProfile != null) {
+ evaluation.setCandidateDataProfile(emergencyProfile);
+ }
networkRequest.setEvaluation(evaluation);
log(evaluation.toString());
return evaluation;
@@ -1685,7 +1724,14 @@ public class DataNetworkController extends Handler {
}
if (!evaluation.containsDisallowedReasons()) {
- evaluation.setCandidateDataProfile(dataProfile);
+ if (transport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN
+ && isEsimBootStrapProvisioningActivated()
+ && isEsimBootStrapMaxDataLimitReached()) {
+ log("BootStrap Sim Data Usage limit reached");
+ evaluation.addDataDisallowedReason(DataDisallowedReason.DATA_LIMIT_REACHED);
+ } else {
+ evaluation.setCandidateDataProfile(dataProfile);
+ }
}
networkRequest.setEvaluation(evaluation);
@@ -1702,6 +1748,61 @@ public class DataNetworkController extends Handler {
}
/**
+ * This method
+ * - At evaluation network request and evaluation data network determines, if
+ * bootstrap sim current data usage reached bootstrap sim max data limit allowed set
+ * at {@link DataConfigManager#getEsimBootStrapMaxDataLimitBytes()}
+ * - Query the current data usage at {@link #getDataUsage()}
+ *
+ * @return true, if bootstrap sim data limit is reached
+ * else false, if bootstrap sim max data limit allowed set is -1(Unlimited) or current
+ * bootstrap sim total data usage is less than bootstrap sim max data limit allowed.
+ *
+ */
+ private boolean isEsimBootStrapMaxDataLimitReached() {
+ long esimBootStrapMaxDataLimitBytes =
+ mDataConfigManager.getEsimBootStrapMaxDataLimitBytes();
+
+ if (esimBootStrapMaxDataLimitBytes < 0L) {
+ return false;
+ }
+
+ log("current bootstrap sim data Usage: " + mBootStrapSimTotalDataUsageBytes);
+ if (mBootStrapSimTotalDataUsageBytes >= esimBootStrapMaxDataLimitBytes) {
+ return true;
+ } else {
+ mBootStrapSimTotalDataUsageBytes = getDataUsage();
+ return mBootStrapSimTotalDataUsageBytes >= esimBootStrapMaxDataLimitBytes;
+ }
+ }
+
+ /**
+ * Query network usage statistics summaries based on {@link
+ * NetworkStatsManager#querySummaryForDevice(NetworkTemplate, long, long)}
+ *
+ * @return Data usage in bytes for the connected networks related to the current subscription
+ */
+ private long getDataUsage() {
+ NetworkStatsManager networkStatsManager =
+ mPhone.getContext().getSystemService(NetworkStatsManager.class);
+
+ if (networkStatsManager != null) {
+ final NetworkTemplate.Builder builder =
+ new NetworkTemplate.Builder(NetworkTemplate.MATCH_MOBILE);
+ final String subscriberId = mPhone.getSubscriberId();
+
+ if (!TextUtils.isEmpty(subscriberId)) {
+ builder.setSubscriberIds(Set.of(subscriberId));
+ NetworkTemplate template = builder.build();
+ final NetworkStats.Bucket ret = networkStatsManager
+ .querySummaryForDevice(template, 0L, System.currentTimeMillis());
+ return ret.getRxBytes() + ret.getTxBytes();
+ }
+ }
+ return 0L;
+ }
+
+ /**
* @return The grouped unsatisfied network requests. The network requests that have the same
* network capabilities is grouped into one {@link NetworkRequestList}.
*/
@@ -1787,6 +1888,25 @@ public class DataNetworkController extends Handler {
evaluation.addDataDisallowedReason(DataDisallowedReason.SERVICE_OPTION_NOT_SUPPORTED);
}
+ // Check whether data limit reached for bootstrap sim, else re-evaluate based on the timer
+ // set.
+ if (isEsimBootStrapProvisioningActivated()
+ && dataNetwork.getTransport() == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
+ if (isEsimBootStrapMaxDataLimitReached()) {
+ log("BootStrap Sim Data Usage limit reached");
+ evaluation.addDataDisallowedReason(DataDisallowedReason.DATA_LIMIT_REACHED);
+ } else {
+ if (!hasMessages(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS)) {
+ sendMessageDelayed(obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS,
+ DataEvaluationReason.CHECK_DATA_USAGE),
+ REEVALUATE_BOOTSTRAP_SIM_DATA_USAGE_MILLIS);
+ } else {
+ log("skip scheduling evaluating existing data networks since already"
+ + "scheduled");
+ }
+ }
+ }
+
// Check if there are other network that has higher priority, and only single data network
// is allowed.
if (isOnlySingleDataNetworkAllowed(dataNetwork.getTransport())
@@ -2169,6 +2289,8 @@ public class DataNetworkController extends Handler {
return DataNetwork.TEAR_DOWN_REASON_ONLY_ALLOWED_SINGLE_NETWORK;
case HANDOVER_RETRY_STOPPED:
return DataNetwork.TEAR_DOWN_REASON_HANDOVER_FAILED;
+ case DATA_LIMIT_REACHED:
+ return DataNetwork.TEAR_DOWN_REASON_DATA_LIMIT_REACHED;
}
}
return DataNetwork.TEAR_DOWN_REASON_NONE;
@@ -2675,6 +2797,14 @@ public class DataNetworkController extends Handler {
DataNetworkController.this.onRetryUnsatisfiedNetworkRequest(
networkRequest);
}
+
+ @Override
+ public void onQosSessionsChanged(
+ @NonNull List<QosBearerSession> qosBearerSessions) {
+ mDataNetworkControllerCallbacks.forEach(
+ callback -> callback.invokeFromExecutor(() ->
+ callback.onQosSessionsChanged(qosBearerSessions)));
+ }
}
));
if (!mAnyDataNetworkExisting) {
@@ -2797,6 +2927,12 @@ public class DataNetworkController extends Handler {
+ TelephonyUtils.dataStateToString(mImsDataNetworkState) + " to CONNECTED.");
mImsDataNetworkState = TelephonyManager.DATA_CONNECTED;
}
+
+ if (isEsimBootStrapProvisioningActivated()) {
+ sendMessageDelayed(obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS,
+ DataEvaluationReason.CHECK_DATA_USAGE),
+ REEVALUATE_BOOTSTRAP_SIM_DATA_USAGE_MILLIS);
+ }
}
/**
@@ -4052,6 +4188,7 @@ public class DataNetworkController extends Handler {
.map(TelephonyManager::getNetworkTypeName).collect(Collectors.joining(",")));
pw.println("mImsThrottleCounter=" + mImsThrottleCounter);
pw.println("mNetworkUnwantedCounter=" + mNetworkUnwantedCounter);
+ pw.println("mBootStrapSimTotalDataUsageBytes=" + mBootStrapSimTotalDataUsageBytes);
pw.println("Local logs:");
pw.increaseIndent();
mLocalLog.dump(fd, pw, args);
diff --git a/src/java/com/android/internal/telephony/data/DataProfileManager.java b/src/java/com/android/internal/telephony/data/DataProfileManager.java
index b4055a3e4c..51fc71babb 100644
--- a/src/java/com/android/internal/telephony/data/DataProfileManager.java
+++ b/src/java/com/android/internal/telephony/data/DataProfileManager.java
@@ -34,6 +34,7 @@ import android.telephony.Annotation;
import android.telephony.Annotation.NetworkType;
import android.telephony.AnomalyReporter;
import android.telephony.CarrierConfigManager;
+import android.telephony.NetworkRegistrationInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.TelephonyManager.SimState;
@@ -286,7 +287,8 @@ public class DataProfileManager extends Handler {
log("Added " + dataProfile);
isInternetSupported |= apn.canHandleType(ApnSetting.TYPE_DEFAULT);
- if (mDataConfigManager.isApnConfigAnomalyReportEnabled()) {
+ if (mDataConfigManager.isApnConfigAnomalyReportEnabled()
+ && apn.getEditedStatus() == Telephony.Carriers.UNEDITED) {
checkApnSetting(apn);
}
}
@@ -818,8 +820,13 @@ public class DataProfileManager extends Handler {
})
.collect(Collectors.toList());
if (dataProfiles.size() == 0) {
+ String ntnReason = "";
+ if (mFeatureFlags.carrierEnabledSatelliteFlag()) {
+ ntnReason = " and infrastructure for "
+ + NetworkRegistrationInfo.isNonTerrestrialNetworkToString(isNtn);
+ }
log("Can't find any data profile for network type "
- + TelephonyManager.getNetworkTypeName(networkType));
+ + TelephonyManager.getNetworkTypeName(networkType) + ntnReason);
return null;
}
diff --git a/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java
index c66aebc1ef..e3eed00479 100644
--- a/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java
+++ b/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java
@@ -33,7 +33,7 @@ import android.telephony.DisconnectCause;
import android.telephony.DomainSelectionService;
import android.telephony.DomainSelectionService.EmergencyScanType;
import android.telephony.DomainSelector;
-import android.telephony.EmergencyRegResult;
+import android.telephony.EmergencyRegistrationResult;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.data.ApnSetting;
import android.util.LocalLog;
@@ -66,6 +66,8 @@ public class DomainSelectionConnection {
protected static final int EVENT_QUALIFIED_NETWORKS_CHANGED = 2;
protected static final int EVENT_SERVICE_CONNECTED = 3;
protected static final int EVENT_SERVICE_BINDING_TIMEOUT = 4;
+ protected static final int EVENT_RESET_NETWORK_SCAN_DONE = 5;
+ protected static final int EVENT_LAST = EVENT_RESET_NETWORK_SCAN_DONE;
private static final int DEFAULT_BIND_RETRY_TIMEOUT_MS = 4 * 1000;
@@ -73,6 +75,7 @@ public class DomainSelectionConnection {
private static final int STATUS_DOMAIN_SELECTED = 1 << 1;
private static final int STATUS_WAIT_BINDING = 1 << 2;
private static final int STATUS_WAIT_SCAN_RESULT = 1 << 3;
+ private static final int STATUS_WAIT_RESET_SCAN_RESULT = 1 << 4;
/** Callback to receive responses from DomainSelectionConnection. */
public interface DomainSelectionConnectionCallback {
@@ -85,6 +88,16 @@ public class DomainSelectionConnection {
void onSelectionTerminated(@DisconnectCauses int cause);
}
+ private static class ScanRequest {
+ final int[] mPreferredNetworks;
+ final int mScanType;
+
+ ScanRequest(int[] preferredNetworks, int scanType) {
+ mPreferredNetworks = preferredNetworks;
+ mScanType = scanType;
+ }
+ }
+
/**
* A wrapper class for {@link ITransportSelectorCallback} interface.
*/
@@ -95,7 +108,7 @@ public class DomainSelectionConnection {
mDomainSelector = selector;
if (checkState(STATUS_DISPOSED)) {
try {
- selector.cancelSelection();
+ selector.finishSelection();
} catch (RemoteException e) {
// ignore exception
}
@@ -117,47 +130,49 @@ public class DomainSelectionConnection {
}
@Override
- public @NonNull IWwanSelectorCallback onWwanSelected() {
+ public void onWwanSelectedAsync(@NonNull final ITransportSelectorResultCallback cb) {
synchronized (mLock) {
+ if (checkState(STATUS_DISPOSED)) {
+ return;
+ }
if (mWwanSelectorCallback == null) {
mWwanSelectorCallback = new WwanSelectorCallbackAdaptor();
}
- if (checkState(STATUS_DISPOSED)) {
- return mWwanSelectorCallback;
+ if (mIsTestMode || !mIsEmergency
+ || (mSelectorType != DomainSelectionService.SELECTOR_TYPE_CALLING)) {
+ initHandler();
+ mHandler.post(() -> {
+ onWwanSelectedAsyncInternal(cb);
+ });
+ } else {
+ Thread workerThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ onWwanSelectedAsyncInternal(cb);
+ }
+ });
+ workerThread.start();
}
- DomainSelectionConnection.this.onWwanSelected();
- return mWwanSelectorCallback;
}
}
- @Override
- public void onWwanSelectedAsync(@NonNull final ITransportSelectorResultCallback cb) {
+ private void onWwanSelectedAsyncInternal(
+ @NonNull final ITransportSelectorResultCallback cb) {
synchronized (mLock) {
if (checkState(STATUS_DISPOSED)) {
return;
}
- if (mWwanSelectorCallback == null) {
- mWwanSelectorCallback = new WwanSelectorCallbackAdaptor();
+ }
+ DomainSelectionConnection.this.onWwanSelected();
+ try {
+ cb.onCompleted(mWwanSelectorCallback);
+ } catch (RemoteException e) {
+ loge("onWwanSelectedAsync executor exception=" + e);
+ synchronized (mLock) {
+ // Since remote service is not available,
+ // wait for binding or timeout.
+ waitForServiceBinding(null);
}
- initHandler();
- mHandler.post(() -> {
- synchronized (mLock) {
- if (checkState(STATUS_DISPOSED)) {
- return;
- }
- DomainSelectionConnection.this.onWwanSelected();
- }
- try {
- cb.onCompleted(mWwanSelectorCallback);
- } catch (RemoteException e) {
- loge("onWwanSelectedAsync executor exception=" + e);
- synchronized (mLock) {
- // Since remote service is not available,
- // wait for binding or timeout.
- waitForServiceBinding(null);
- }
- }
- });
}
}
@@ -180,7 +195,8 @@ public class DomainSelectionConnection {
@Override
public void onRequestEmergencyNetworkScan(
@NonNull @RadioAccessNetworkType int[] preferredNetworks,
- @EmergencyScanType int scanType, @NonNull IWwanSelectorResultCallback cb) {
+ @EmergencyScanType int scanType, boolean resetScan,
+ @NonNull IWwanSelectorResultCallback cb) {
synchronized (mLock) {
if (checkState(STATUS_DISPOSED)) {
return;
@@ -190,7 +206,7 @@ public class DomainSelectionConnection {
mHandler.post(() -> {
synchronized (mLock) {
DomainSelectionConnection.this.onRequestEmergencyNetworkScan(
- preferredNetworks, scanType);
+ preferredNetworks, scanType, resetScan);
}
});
}
@@ -232,7 +248,7 @@ public class DomainSelectionConnection {
switch (msg.what) {
case EVENT_EMERGENCY_NETWORK_SCAN_RESULT:
ar = (AsyncResult) msg.obj;
- EmergencyRegResult regResult = (EmergencyRegResult) ar.result;
+ EmergencyRegistrationResult regResult = (EmergencyRegistrationResult) ar.result;
if (DBG) logd("EVENT_EMERGENCY_NETWORK_SCAN_RESULT result=" + regResult);
synchronized (mLock) {
clearState(STATUS_WAIT_SCAN_RESULT);
@@ -275,6 +291,17 @@ public class DomainSelectionConnection {
}
}
break;
+ case EVENT_RESET_NETWORK_SCAN_DONE:
+ synchronized (mLock) {
+ clearState(STATUS_WAIT_RESET_SCAN_RESULT);
+ if (checkState(STATUS_DISPOSED)
+ || (mPendingScanRequest == null)) {
+ return;
+ }
+ onRequestEmergencyNetworkScan(mPendingScanRequest.mPreferredNetworks,
+ mPendingScanRequest.mScanType, false);
+ }
+ break;
default:
loge("handleMessage unexpected msg=" + msg.what);
break;
@@ -320,6 +347,10 @@ public class DomainSelectionConnection {
private @NonNull AndroidFuture<Integer> mOnComplete;
+ private @Nullable ScanRequest mPendingScanRequest;
+
+ private boolean mIsTestMode = false;
+
/**
* Creates an instance.
*
@@ -364,7 +395,7 @@ public class DomainSelectionConnection {
*
* @return The {@link IWwanSelectorResultCallback} interface.
*/
- public @Nullable IWwanSelectorResultCallback getEmergencyRegResultCallback() {
+ public @Nullable IWwanSelectorResultCallback getWwanSelectorResultCallback() {
return mResultCallback;
}
@@ -449,10 +480,11 @@ public class DomainSelectionConnection {
*
* @param preferredNetworks The ordered list of preferred networks to scan.
* @param scanType Indicates the scan preference, such as full service or limited service.
+ * @param resetScan Indicates that the previous scan result shall be reset before scanning.
*/
public void onRequestEmergencyNetworkScan(
@NonNull @RadioAccessNetworkType int[] preferredNetworks,
- @EmergencyScanType int scanType) {
+ @EmergencyScanType int scanType, boolean resetScan) {
// Can be overridden if required
synchronized (mLock) {
@@ -464,6 +496,29 @@ public class DomainSelectionConnection {
return;
}
+ if (checkState(STATUS_WAIT_RESET_SCAN_RESULT)) {
+ if (mPendingScanRequest != null) {
+ /* Consecutive scan requests without cancellation is not an expected use case.
+ * DomainSelector should cancel the previous request or wait for the result
+ * before requesting a new scan.*/
+ logi("onRequestEmergencyNetworkScan consecutive scan requests");
+ return;
+ } else {
+ // The reset has not been completed.
+ // case1) Long delay in cancelEmergencyNetworkScan by modem.
+ // case2) A consecutive scan requests with short interval from DomainSelector.
+ logi("onRequestEmergencyNetworkScan reset not completed");
+ }
+ mPendingScanRequest = new ScanRequest(preferredNetworks, scanType);
+ return;
+ } else if (resetScan) {
+ setState(STATUS_WAIT_RESET_SCAN_RESULT);
+ mPendingScanRequest = new ScanRequest(preferredNetworks, scanType);
+ mPhone.cancelEmergencyNetworkScan(resetScan,
+ mHandler.obtainMessage(EVENT_RESET_NETWORK_SCAN_DONE));
+ return;
+ }
+
if (!mRegisteredRegistrant) {
mPhone.registerForEmergencyNetworkScan(mHandler,
EVENT_EMERGENCY_NETWORK_SCAN_RESULT, null);
@@ -471,6 +526,7 @@ public class DomainSelectionConnection {
}
setState(STATUS_WAIT_SCAN_RESULT);
mPhone.triggerEmergencyNetworkScan(preferredNetworks, scanType, null);
+ mPendingScanRequest = null;
}
}
@@ -506,6 +562,7 @@ public class DomainSelectionConnection {
}
private void onCancel(boolean resetScan) {
+ mPendingScanRequest = null;
if (checkState(STATUS_WAIT_SCAN_RESULT)) {
clearState(STATUS_WAIT_SCAN_RESULT);
mPhone.cancelEmergencyNetworkScan(resetScan, null);
@@ -517,17 +574,7 @@ public class DomainSelectionConnection {
* to clean up all ongoing operations with the framework.
*/
public void cancelSelection() {
- synchronized (mLock) {
- try {
- if (mDomainSelector != null) {
- mDomainSelector.cancelSelection();
- }
- } catch (RemoteException e) {
- loge("cancelSelection exception=" + e);
- } finally {
- dispose();
- }
- }
+ finishSelection();
}
/**
@@ -732,6 +779,16 @@ public class DomainSelectionConnection {
}
/**
+ * Set whether it is unit test or not.
+ *
+ * @param testMode Indicates whether it is unit test or not.
+ */
+ @VisibleForTesting
+ public void setTestMode(boolean testMode) {
+ mIsTestMode = testMode;
+ }
+
+ /**
* Dumps local log.
*/
public void dump(@NonNull PrintWriter printWriter) {
diff --git a/src/java/com/android/internal/telephony/domainselection/DomainSelectionController.java b/src/java/com/android/internal/telephony/domainselection/DomainSelectionController.java
index 6d64a31223..ee8517da19 100644
--- a/src/java/com/android/internal/telephony/domainselection/DomainSelectionController.java
+++ b/src/java/com/android/internal/telephony/domainselection/DomainSelectionController.java
@@ -101,6 +101,7 @@ public class DomainSelectionController {
private ExponentialBackoff mBackoff;
private boolean mBackoffStarted = false;
+ private boolean mUnbind = false;
// Retry the bind to the DomainSelectionService that has died after mBindRetry timeout.
private Runnable mRestartBindingRunnable = new Runnable() {
@@ -470,12 +471,14 @@ public class DomainSelectionController {
*/
public boolean bind(@NonNull ComponentName componentName) {
mComponentName = componentName;
+ mUnbind = false;
return bind();
}
private boolean bind() {
logd("bind isBindingOrBound=" + mIsBound);
synchronized (mLock) {
+ if (mUnbind) return false;
if (!mIsBound) {
mIsBound = true;
Intent serviceIntent = new Intent(DomainSelectionService.SERVICE_INTERFACE)
@@ -512,6 +515,7 @@ public class DomainSelectionController {
*/
public void unbind() {
synchronized (mLock) {
+ mUnbind = true;
stopBackoffTimer();
mIsBound = false;
setServiceController(null);
@@ -548,7 +552,8 @@ public class DomainSelectionController {
}
private void notifyBindFailure() {
- logi("notifyBindFailure " + mBackoffStarted);
+ logi("notifyBindFailure started=" + mBackoffStarted + ", unbind=" + mUnbind);
+ if (mUnbind) return;
if (mBackoffStarted) {
mBackoff.notifyFailed();
} else {
diff --git a/src/java/com/android/internal/telephony/domainselection/DomainSelectionResolver.java b/src/java/com/android/internal/telephony/domainselection/DomainSelectionResolver.java
index 8a0dcf5c48..410f89b01d 100644
--- a/src/java/com/android/internal/telephony/domainselection/DomainSelectionResolver.java
+++ b/src/java/com/android/internal/telephony/domainselection/DomainSelectionResolver.java
@@ -24,6 +24,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
+import android.os.SystemProperties;
import android.telephony.DomainSelectionService;
import android.text.TextUtils;
import android.util.IndentingPrintWriter;
@@ -34,6 +35,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;
import com.android.internal.telephony.flags.Flags;
+import com.android.internal.telephony.util.TelephonyUtils;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -48,6 +50,10 @@ public class DomainSelectionResolver {
@VisibleForTesting
protected static final String PACKAGE_NAME_NONE = "none";
private static final String TAG = DomainSelectionResolver.class.getSimpleName();
+ private static final boolean DBG = TelephonyUtils.IS_DEBUGGABLE;
+ /** For test purpose only with userdebug release */
+ private static final String PROP_DISABLE_DOMAIN_SELECTION =
+ "telephony.test.disable_domain_selection";
private static DomainSelectionResolver sInstance = null;
/**
@@ -132,6 +138,10 @@ public class DomainSelectionResolver {
* {@code false} otherwise.
*/
public boolean isDomainSelectionSupported() {
+ if (DBG && SystemProperties.getBoolean(PROP_DISABLE_DOMAIN_SELECTION, false)) {
+ logi("Disabled for test");
+ return false;
+ }
return mDefaultComponentName != null && PhoneFactory.getDefaultPhone()
.getHalVersion(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1);
}
@@ -198,7 +208,6 @@ public class DomainSelectionResolver {
* @return {@code true} if the requested operation is successfully done,
* {@code false} otherwise.
*/
- @VisibleForTesting
public boolean setDomainSelectionServiceOverride(@NonNull ComponentName componentName) {
if (mController == null) {
logd("Controller is not initialized.");
@@ -224,7 +233,6 @@ public class DomainSelectionResolver {
* @return {@code true} if the requested operation is successfully done,
* {@code false} otherwise.
*/
- @VisibleForTesting
public boolean clearDomainSelectionServiceOverride() {
if (mController == null) {
logd("Controller is not initialized.");
diff --git a/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java
index af4b03aacf..cb4ddab76a 100644
--- a/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java
+++ b/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java
@@ -28,12 +28,14 @@ import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_E
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.net.Uri;
+import android.telecom.PhoneAccount;
import android.telephony.AccessNetworkConstants.AccessNetworkType;
import android.telephony.AccessNetworkConstants.TransportType;
import android.telephony.Annotation.DisconnectCauses;
import android.telephony.Annotation.NetCapability;
import android.telephony.DomainSelectionService;
-import android.telephony.EmergencyRegResult;
+import android.telephony.EmergencyRegistrationResult;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.data.ApnSetting;
import android.telephony.ims.ImsReasonInfo;
@@ -107,7 +109,7 @@ public class EmergencyCallDomainSelectionConnection extends DomainSelectionConne
/** {@inheritDoc} */
@Override
public void onWwanSelected() {
- mEmergencyStateTracker.onEmergencyTransportChanged(
+ mEmergencyStateTracker.onEmergencyTransportChangedAndWait(
EmergencyStateTracker.EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WWAN);
}
@@ -202,6 +204,7 @@ public class EmergencyCallDomainSelectionConnection extends DomainSelectionConne
* @param exited {@code true} if the request caused the device to move out of airplane mode.
* @param callId The call identifier.
* @param number The dialed number.
+ * @param isTest Indicates it's a test emergency number.
* @param callFailCause The reason why the last CS attempt failed.
* @param imsReasonInfo The reason why the last PS attempt failed.
* @param emergencyRegResult The current registration result for emergency services.
@@ -209,20 +212,21 @@ public class EmergencyCallDomainSelectionConnection extends DomainSelectionConne
*/
public static @NonNull DomainSelectionService.SelectionAttributes getSelectionAttributes(
int slotId, int subId, boolean exited,
- @NonNull String callId, @NonNull String number, int callFailCause,
- @Nullable ImsReasonInfo imsReasonInfo,
- @Nullable EmergencyRegResult emergencyRegResult) {
+ @NonNull String callId, @NonNull String number, boolean isTest,
+ int callFailCause, @Nullable ImsReasonInfo imsReasonInfo,
+ @Nullable EmergencyRegistrationResult emergencyRegResult) {
DomainSelectionService.SelectionAttributes.Builder builder =
new DomainSelectionService.SelectionAttributes.Builder(
slotId, subId, SELECTOR_TYPE_CALLING)
.setEmergency(true)
+ .setTestEmergencyNumber(isTest)
.setExitedFromAirplaneMode(exited)
.setCallId(callId)
- .setNumber(number)
+ .setAddress(Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null))
.setCsDisconnectCause(callFailCause);
if (imsReasonInfo != null) builder.setPsDisconnectCause(imsReasonInfo);
- if (emergencyRegResult != null) builder.setEmergencyRegResult(emergencyRegResult);
+ if (emergencyRegResult != null) builder.setEmergencyRegistrationResult(emergencyRegResult);
return builder.build();
}
@@ -234,13 +238,15 @@ public class EmergencyCallDomainSelectionConnection extends DomainSelectionConne
if (attr == null) return null;
DomainSelectionService.SelectionAttributes.Builder builder =
new DomainSelectionService.SelectionAttributes.Builder(
- attr.getSlotId(), attr.getSubId(), SELECTOR_TYPE_CALLING)
+ attr.getSlotIndex(), attr.getSubscriptionId(), SELECTOR_TYPE_CALLING)
.setCallId(attr.getCallId())
- .setNumber(attr.getNumber())
+ .setAddress(attr.getAddress())
.setVideoCall(attr.isVideoCall())
.setEmergency(true)
+ .setTestEmergencyNumber(attr.isTestEmergencyNumber())
.setExitedFromAirplaneMode(attr.isExitedFromAirplaneMode())
- .setEmergencyRegResult(new EmergencyRegResult(AccessNetworkType.UNKNOWN,
+ .setEmergencyRegistrationResult(
+ new EmergencyRegistrationResult(AccessNetworkType.UNKNOWN,
NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN,
NetworkRegistrationInfo.DOMAIN_UNKNOWN, false, false, 0, 0,
"", "", ""));
diff --git a/src/java/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnection.java
index 0532a05d41..0fd92017e3 100644
--- a/src/java/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnection.java
+++ b/src/java/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnection.java
@@ -20,6 +20,8 @@ import static android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.net.Uri;
+import android.telecom.PhoneAccount;
import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
import android.telephony.Annotation.DisconnectCauses;
import android.telephony.DisconnectCause;
@@ -78,7 +80,7 @@ public class NormalCallDomainSelectionConnection extends DomainSelectionConnecti
/** {@inheritDoc} */
@Override
public void onRequestEmergencyNetworkScan(@RadioAccessNetworkType int[] preferredNetworks,
- @EmergencyScanType int scanType) {
+ @EmergencyScanType int scanType, boolean resetScan) {
// Not expected with normal calling.
// Override to prevent abnormal behavior.
}
@@ -119,7 +121,7 @@ public class NormalCallDomainSelectionConnection extends DomainSelectionConnecti
slotId, subId, SELECTOR_TYPE_CALLING)
.setEmergency(false)
.setCallId(callId)
- .setNumber(number)
+ .setAddress(Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null))
.setCsDisconnectCause(callFailCause)
.setVideoCall(isVideoCall);
diff --git a/src/java/com/android/internal/telephony/domainselection/OWNERS b/src/java/com/android/internal/telephony/domainselection/OWNERS
index b9112be19b..2a7677001b 100644
--- a/src/java/com/android/internal/telephony/domainselection/OWNERS
+++ b/src/java/com/android/internal/telephony/domainselection/OWNERS
@@ -6,3 +6,4 @@ avinashmp@google.com
mkoon@google.com
seheele@google.com
radhikaagrawal@google.com
+jdyou@google.com
diff --git a/src/java/com/android/internal/telephony/domainselection/SmsDomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/SmsDomainSelectionConnection.java
index 36a7b17384..b3f49243ca 100644
--- a/src/java/com/android/internal/telephony/domainselection/SmsDomainSelectionConnection.java
+++ b/src/java/com/android/internal/telephony/domainselection/SmsDomainSelectionConnection.java
@@ -53,17 +53,6 @@ public class SmsDomainSelectionConnection extends DomainSelectionConnection {
if (mCallback != null) mCallback.onSelectionTerminated(cause);
}
- @Override
- public void finishSelection() {
- CompletableFuture<Integer> future = getCompletableFuture();
-
- if (future != null && !future.isDone()) {
- cancelSelection();
- } else {
- super.finishSelection();
- }
- }
-
/**
* Requests a domain selection for SMS.
*
diff --git a/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java b/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
index f3c0a6c485..a35cccff09 100644
--- a/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
+++ b/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
@@ -35,6 +35,7 @@ import android.os.Looper;
import android.os.Message;
import android.os.PersistableBundle;
import android.os.PowerManager;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.preference.PreferenceManager;
import android.provider.Settings;
@@ -43,7 +44,7 @@ import android.telephony.AccessNetworkConstants;
import android.telephony.Annotation.DisconnectCauses;
import android.telephony.CarrierConfigManager;
import android.telephony.DisconnectCause;
-import android.telephony.EmergencyRegResult;
+import android.telephony.EmergencyRegistrationResult;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.PreciseDataConnectionState;
import android.telephony.ServiceState;
@@ -84,13 +85,17 @@ public class EmergencyStateTracker {
* 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;
+ private static final int DEFAULT_DATA_SWITCH_TIMEOUT_MS = 1 * 1000;
+ @VisibleForTesting
+ public static final int DEFAULT_WAIT_FOR_IN_SERVICE_TIMEOUT_MS = 3 * 1000;
/** Default value for if Emergency Callback Mode is supported. */
private static final boolean DEFAULT_EMERGENCY_CALLBACK_MODE_SUPPORTED = true;
/** Default Emergency Callback Mode exit timeout value. */
private static final long DEFAULT_ECM_EXIT_TIMEOUT_MS = 300000;
private static final int DEFAULT_EPDN_DISCONNECTION_TIMEOUT_MS = 500;
+ private static final int DEFAULT_TRANSPORT_CHANGE_TIMEOUT_MS = 1 * 1000;
+
/** The emergency types used when setting the emergency mode on modem. */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = "EMERGENCY_TYPE_",
@@ -117,7 +122,7 @@ public class EmergencyStateTracker {
@EmergencyConstants.EmergencyMode
private int mEmergencyMode = MODE_EMERGENCY_NONE;
private boolean mWasEmergencyModeSetOnModem;
- private EmergencyRegResult mLastEmergencyRegResult;
+ private EmergencyRegistrationResult mLastEmergencyRegistrationResult;
private boolean mIsEmergencyModeInProgress;
private boolean mIsEmergencyCallStartedDuringEmergencySms;
@@ -126,13 +131,13 @@ public class EmergencyStateTracker {
// A runnable which is used to automatically exit from Ecm after a period of time.
private final Runnable mExitEcmRunnable = this::exitEmergencyCallbackMode;
// Tracks emergency calls by callId that have reached {@link Call.State#ACTIVE}.
- private final Set<String> mActiveEmergencyCalls = new ArraySet<>();
+ private final Set<android.telecom.Connection> mActiveEmergencyCalls = new ArraySet<>();
private Phone mPhoneToExit;
private int mPdnDisconnectionTimeoutMs = DEFAULT_EPDN_DISCONNECTION_TIMEOUT_MS;
private final Object mLock = new Object();
private Phone mPhone;
- // Tracks ongoing emergency callId to handle a second emergency call
- private String mOngoingCallId;
+ // Tracks ongoing emergency connection to handle a second emergency call
+ private android.telecom.Connection mOngoingConnection;
// Domain of the active emergency call. Assuming here that there will only be one domain active.
private int mEmergencyCallDomain = NetworkRegistrationInfo.DOMAIN_UNKNOWN;
private CompletableFuture<Integer> mCallEmergencyModeFuture;
@@ -148,6 +153,8 @@ public class EmergencyStateTracker {
private CompletableFuture<Integer> mSmsEmergencyModeFuture;
private boolean mIsTestEmergencyNumberForSms;
+ private CompletableFuture<Boolean> mEmergencyTransportChangedFuture;
+
private final android.util.ArrayMap<Integer, Boolean> mNoSimEcbmSupported =
new android.util.ArrayMap<>();
private final CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener =
@@ -286,14 +293,18 @@ public class EmergencyStateTracker {
Rlog.v(TAG, "MSG_SET_EMERGENCY_MODE_DONE for "
+ emergencyTypeToString(emergencyType));
if (ar.exception == null) {
- mLastEmergencyRegResult = (EmergencyRegResult) ar.result;
+ mLastEmergencyRegistrationResult = (EmergencyRegistrationResult) ar.result;
} else {
- mLastEmergencyRegResult = null;
- Rlog.w(TAG, "LastEmergencyRegResult not set. AsyncResult.exception: "
+ mLastEmergencyRegistrationResult = null;
+ Rlog.w(TAG,
+ "LastEmergencyRegistrationResult not set. AsyncResult.exception: "
+ ar.exception);
}
setEmergencyModeInProgress(false);
+ // Transport changed from WLAN to WWAN or CALLBACK to WWAN
+ maybeNotifyTransportChangeCompleted(emergencyType, false);
+
if (emergencyType == EMERGENCY_TYPE_CALL) {
setIsInEmergencyCall(true);
completeEmergencyMode(emergencyType);
@@ -496,24 +507,25 @@ public class EmergencyStateTracker {
* Handles turning on radio and switching DDS.
*
* @param phone the {@code Phone} on which to process the emergency call.
- * @param callId the call id on which to process the emergency call.
+ * @param c the {@code Connection} on which to process the emergency call.
* @param isTestEmergencyNumber whether this is a test emergency number.
* @return a {@code CompletableFuture} that results in {@code DisconnectCause.NOT_DISCONNECTED}
* if emergency call successfully started.
*/
public CompletableFuture<Integer> startEmergencyCall(@NonNull Phone phone,
- @NonNull String callId, boolean isTestEmergencyNumber) {
- Rlog.i(TAG, "startEmergencyCall: phoneId=" + phone.getPhoneId() + ", callId=" + callId);
+ @NonNull android.telecom.Connection c, boolean isTestEmergencyNumber) {
+ Rlog.i(TAG, "startEmergencyCall: phoneId=" + phone.getPhoneId()
+ + ", callId=" + c.getTelecomCallId());
if (mPhone != null) {
// Create new future to return as to not interfere with any uncompleted futures.
// Case1) When 2nd emergency call is initiated during an active call on the same phone.
// Case2) While the device is in ECBM, an emergency call is initiated on the same phone.
if (isSamePhone(mPhone, phone) && (!mActiveEmergencyCalls.isEmpty() || isInEcm())) {
- mOngoingCallId = callId;
+ mOngoingConnection = c;
mIsTestEmergencyNumber = isTestEmergencyNumber;
// Ensure that domain selector requests scan.
- mLastEmergencyRegResult = new EmergencyRegResult(
+ mLastEmergencyRegistrationResult = new EmergencyRegistrationResult(
AccessNetworkConstants.AccessNetworkType.UNKNOWN,
NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN,
NetworkRegistrationInfo.DOMAIN_UNKNOWN, false, false, 0, 0, "", "", "");
@@ -545,13 +557,13 @@ public class EmergencyStateTracker {
}
mPhone = phone;
- mOngoingCallId = callId;
+ mOngoingConnection = c;
mIsTestEmergencyNumber = isTestEmergencyNumber;
return mCallEmergencyModeFuture;
}
mPhone = phone;
- mOngoingCallId = callId;
+ mOngoingConnection = c;
mIsTestEmergencyNumber = isTestEmergencyNumber;
turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL, mIsTestEmergencyNumber);
return mCallEmergencyModeFuture;
@@ -564,13 +576,13 @@ public class EmergencyStateTracker {
* Enter ECM only once all active emergency calls have ended. If a call never reached
* {@link Call.State#ACTIVE}, then no need to enter ECM.
*
- * @param callId the call id on which to end the emergency call.
+ * @param c the emergency call disconnected.
*/
- public void endCall(@NonNull String callId) {
- boolean wasActive = mActiveEmergencyCalls.remove(callId);
+ public void endCall(@NonNull android.telecom.Connection c) {
+ boolean wasActive = mActiveEmergencyCalls.remove(c);
- if (Objects.equals(mOngoingCallId, callId)) {
- mOngoingCallId = null;
+ if (Objects.equals(mOngoingConnection, c)) {
+ mOngoingConnection = null;
mOngoingCallProperties = 0;
}
@@ -578,11 +590,11 @@ public class EmergencyStateTracker {
&& isEmergencyCallbackModeSupported()) {
enterEmergencyCallbackMode();
- if (mOngoingCallId == null) {
+ if (mOngoingConnection == null) {
mIsEmergencyCallStartedDuringEmergencySms = false;
mCallEmergencyModeFuture = null;
}
- } else if (mOngoingCallId == null) {
+ } else if (mOngoingConnection == null) {
if (isInEcm()) {
mIsEmergencyCallStartedDuringEmergencySms = false;
mCallEmergencyModeFuture = null;
@@ -596,6 +608,9 @@ public class EmergencyStateTracker {
clearEmergencyCallInfo();
}
}
+
+ // Release any blocked thread immediately
+ maybeNotifyTransportChangeCompleted(EMERGENCY_TYPE_CALL, true);
}
private void clearEmergencyCallInfo() {
@@ -603,7 +618,7 @@ public class EmergencyStateTracker {
mIsTestEmergencyNumber = false;
mIsEmergencyCallStartedDuringEmergencySms = false;
mCallEmergencyModeFuture = null;
- mOngoingCallId = null;
+ mOngoingConnection = null;
mOngoingCallProperties = 0;
mPhone = null;
}
@@ -616,10 +631,10 @@ public class EmergencyStateTracker {
Rlog.e(TAG, "DDS Switch failed.");
}
// Once radio is on and DDS switched, must call setEmergencyMode() before selecting
- // emergency domain. EmergencyRegResult is required to determine domain and this is the
- // only API that can receive it before starting domain selection. Once domain selection
- // is finished, the actual emergency mode will be set when onEmergencyTransportChanged()
- // is called.
+ // emergency domain. EmergencyRegistrationResult is required to determine domain and
+ // this is the only API that can receive it before starting domain selection.
+ // Once domain selection is finished, the actual emergency mode will be set when
+ // onEmergencyTransportChanged() is called.
setEmergencyMode(phone, emergencyType, MODE_EMERGENCY_WWAN,
MSG_SET_EMERGENCY_MODE_DONE);
});
@@ -639,14 +654,15 @@ public class EmergencyStateTracker {
+ emergencyTypeToString(emergencyType));
if (mEmergencyMode == mode) {
+ // Initial transport selection of DomainSelector
+ maybeNotifyTransportChangeCompleted(emergencyType, false);
return;
}
mEmergencyMode = mode;
setEmergencyModeInProgress(true);
Message m = mHandler.obtainMessage(msg, Integer.valueOf(emergencyType));
- if ((mIsTestEmergencyNumber && emergencyType == EMERGENCY_TYPE_CALL)
- || (mIsTestEmergencyNumberForSms && emergencyType == EMERGENCY_TYPE_SMS)) {
+ if (mIsTestEmergencyNumberForSms && emergencyType == EMERGENCY_TYPE_SMS) {
Rlog.d(TAG, "TestEmergencyNumber for " + emergencyTypeToString(emergencyType)
+ ": Skipping setting emergency mode on modem.");
// Send back a response for the command, but with null information
@@ -808,9 +824,72 @@ public class EmergencyStateTracker {
}
}
- /** Returns last {@link EmergencyRegResult} as set by {@code setEmergencyMode()}. */
- public EmergencyRegResult getEmergencyRegResult() {
- return mLastEmergencyRegResult;
+ /** Returns last {@link EmergencyRegistrationResult} as set by {@code setEmergencyMode()}. */
+ public EmergencyRegistrationResult getEmergencyRegistrationResult() {
+ return mLastEmergencyRegistrationResult;
+ }
+
+ private void waitForTransportChangeCompleted(CompletableFuture<Boolean> future) {
+ if (future != null) {
+ synchronized (future) {
+ if ((mEmergencyMode == MODE_EMERGENCY_NONE)
+ || mHandler.getLooper().isCurrentThread()) {
+ // Do not block the Handler's thread
+ return;
+ }
+ long now = SystemClock.elapsedRealtime();
+ long deadline = now + DEFAULT_TRANSPORT_CHANGE_TIMEOUT_MS;
+ // Guard with while loop to handle spurious wakeups
+ while (!future.isDone() && now < deadline) {
+ try {
+ future.wait(deadline - now);
+ } catch (Exception e) {
+ Rlog.e(TAG, "waitForTransportChangeCompleted wait e=" + e);
+ }
+ now = SystemClock.elapsedRealtime();
+ }
+ }
+ }
+ }
+
+ private void maybeNotifyTransportChangeCompleted(@EmergencyType int emergencyType,
+ boolean enforced) {
+ if (emergencyType != EMERGENCY_TYPE_CALL) {
+ // It's not for the emergency call
+ return;
+ }
+ CompletableFuture<Boolean> future = mEmergencyTransportChangedFuture;
+ if (future != null) {
+ synchronized (future) {
+ if (!future.isDone()
+ && ((!isEmergencyModeInProgress() && mEmergencyMode == MODE_EMERGENCY_WWAN)
+ || enforced)) {
+ future.complete(Boolean.TRUE);
+ future.notifyAll();
+ }
+ }
+ }
+ }
+
+ /**
+ * Handles emergency transport change by setting new emergency mode.
+ *
+ * @param emergencyType the emergency type to identify an emergency call or SMS
+ * @param mode the new emergency mode
+ */
+ public void onEmergencyTransportChangedAndWait(@EmergencyType int emergencyType,
+ @EmergencyConstants.EmergencyMode int mode) {
+ // Wait for the completion of setting MODE_EMERGENCY_WWAN only for emergency calls
+ if (emergencyType == EMERGENCY_TYPE_CALL && mode == MODE_EMERGENCY_WWAN) {
+ CompletableFuture<Boolean> future = new CompletableFuture<>();
+ synchronized (future) {
+ mEmergencyTransportChangedFuture = future;
+ onEmergencyTransportChanged(emergencyType, mode);
+ waitForTransportChangeCompleted(future);
+ }
+ return;
+ }
+ onEmergencyTransportChanged(emergencyType, mode);
}
/**
@@ -842,10 +921,10 @@ public class EmergencyStateTracker {
/**
* Notify the tracker that the emergency call domain has been updated.
* @param phoneType The new PHONE_TYPE_* of the call.
- * @param callId The ID of the call
+ * @param c The connection of the call
*/
- public void onEmergencyCallDomainUpdated(int phoneType, String callId) {
- Rlog.d(TAG, "domain update for callId: " + callId);
+ public void onEmergencyCallDomainUpdated(int phoneType, android.telecom.Connection c) {
+ Rlog.d(TAG, "domain update for callId: " + c.getTelecomCallId());
int domain = -1;
switch(phoneType) {
case (PhoneConstants.PHONE_TYPE_CDMA_LTE):
@@ -873,13 +952,13 @@ public class EmergencyStateTracker {
* Handles emergency call state change.
*
* @param state the new call state
- * @param callId the callId whose state has changed
+ * @param c the call whose state has changed
*/
- public void onEmergencyCallStateChanged(Call.State state, String callId) {
+ public void onEmergencyCallStateChanged(Call.State state, android.telecom.Connection c) {
if (state == Call.State.ACTIVE) {
- mActiveEmergencyCalls.add(callId);
- if (Objects.equals(mOngoingCallId, callId)) {
- Rlog.i(TAG, "call connected " + callId);
+ mActiveEmergencyCalls.add(c);
+ if (Objects.equals(mOngoingConnection, c)) {
+ Rlog.i(TAG, "call connected " + c.getTelecomCallId());
if (mPhone != null
&& isVoWiFi(mOngoingCallProperties)
&& mEmergencyMode == EmergencyConstants.MODE_EMERGENCY_WLAN) {
@@ -894,10 +973,10 @@ public class EmergencyStateTracker {
* Handles the change of emergency call properties.
*
* @param properties the new call properties.
- * @param callId the callId whose state has changed.
+ * @param c the call whose state has changed.
*/
- public void onEmergencyCallPropertiesChanged(int properties, String callId) {
- if (Objects.equals(mOngoingCallId, callId)) {
+ public void onEmergencyCallPropertiesChanged(int properties, android.telecom.Connection c) {
+ if (Objects.equals(mOngoingConnection, c)) {
mOngoingCallProperties = properties;
}
}
@@ -1160,7 +1239,7 @@ public class EmergencyStateTracker {
if (isInEcm()) {
// When the emergency mode is not in MODE_EMERGENCY_CALLBACK,
// it needs to notify the emergency callback mode to modem.
- if (mActiveEmergencyCalls.isEmpty() && mOngoingCallId == null) {
+ if (mActiveEmergencyCalls.isEmpty() && mOngoingConnection == null) {
setEmergencyMode(mPhone, EMERGENCY_TYPE_CALL, MODE_EMERGENCY_CALLBACK,
MSG_SET_EMERGENCY_CALLBACK_MODE_DONE);
}
@@ -1203,8 +1282,8 @@ public class EmergencyStateTracker {
*
* <p>
* Once radio is on and DDS switched, must call setEmergencyMode() before completing the future
- * and selecting emergency domain. EmergencyRegResult is required to determine domain and
- * setEmergencyMode() is the only API that can receive it before starting domain selection.
+ * and selecting emergency domain. EmergencyRegistrationResult is required to determine domain
+ * and setEmergencyMode() is the only API that can receive it before starting domain selection.
* Once domain selection is finished, the actual emergency mode will be set when
* onEmergencyTransportChanged() is called.
*
@@ -1227,6 +1306,11 @@ public class EmergencyStateTracker {
mRadioOnHelper = new RadioOnHelper(mContext);
}
+ final Phone phoneForEmergency = phone;
+ final android.telecom.Connection expectedConnection = mOngoingConnection;
+ final int waitForInServiceTimeout =
+ needToTurnOnRadio ? DEFAULT_WAIT_FOR_IN_SERVICE_TIMEOUT_MS : 0;
+ Rlog.i(TAG, "turnOnRadioAndSwitchDds: timeout=" + waitForInServiceTimeout);
mRadioOnHelper.triggerRadioOnAndListen(new RadioOnStateListener.Callback() {
@Override
public void onComplete(RadioOnStateListener listener, boolean isRadioReady) {
@@ -1241,25 +1325,34 @@ public class EmergencyStateTracker {
completeEmergencyMode(emergencyType, DisconnectCause.POWER_OFF);
}
} else {
+ if (!Objects.equals(mOngoingConnection, expectedConnection)) {
+ Rlog.i(TAG, "onComplete "
+ + expectedConnection.getTelecomCallId() + " canceled.");
+ return;
+ }
switchDdsAndSetEmergencyMode(phone, emergencyType);
}
}
@Override
public boolean isOkToCall(Phone phone, int serviceState, boolean imsVoiceCapable) {
- // We currently only look to make sure that the radio is on before dialing. We
- // should be able to make emergency calls at any time after the radio has been
- // powered on and isn't in the UNAVAILABLE state, even if it is reporting the
- // OUT_OF_SERVICE state.
+ // Wait for normal service state or timeout if required.
+ if (phone == phoneForEmergency
+ && waitForInServiceTimeout > 0
+ && !isNetworkRegistered(phone)) {
+ return false;
+ }
return phone.getServiceStateTracker().isRadioOn()
&& !satelliteController.isSatelliteEnabled();
}
@Override
public boolean onTimeout(Phone phone, int serviceState, boolean imsVoiceCapable) {
- return true;
+ // onTimeout shall be called only with the Phone for emergency
+ return phone.getServiceStateTracker().isRadioOn()
+ && !satelliteController.isSatelliteEnabled();
}
- }, !isTestEmergencyNumber, phone, isTestEmergencyNumber, 0);
+ }, !isTestEmergencyNumber, phone, isTestEmergencyNumber, waitForInServiceTimeout);
} else {
switchDdsAndSetEmergencyMode(phone, emergencyType);
}
@@ -1412,6 +1505,27 @@ public class EmergencyStateTracker {
|| phone.getServiceState().isEmergencyOnly();
}
+ private static boolean isNetworkRegistered(Phone phone) {
+ ServiceState ss = phone.getServiceStateTracker().getServiceState();
+ if (ss != null) {
+ NetworkRegistrationInfo nri = ss.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+ if (nri != null && nri.isNetworkRegistered()) {
+ // PS is IN_SERVICE state.
+ return true;
+ }
+ nri = ss.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_CS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+ if (nri != null && nri.isNetworkRegistered()) {
+ // CS is IN_SERVICE state.
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Checks whether both {@code Phone}s are same or not.
*/
diff --git a/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java b/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java
index 9c4ebfabe3..384112d46b 100644
--- a/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java
+++ b/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java
@@ -96,7 +96,7 @@ public class RadioOnHelper implements RadioOnStateListener.Callback {
continue;
}
- int timeoutCallbackInterval = (forEmergencyCall && phone == phoneForEmergencyCall)
+ int timeoutCallbackInterval = (phone == phoneForEmergencyCall)
? emergencyTimeoutIntervalMillis : 0;
mInProgressListeners.add(mListeners.get(i));
mListeners.get(i).waitForRadioOn(phone, this, forEmergencyCall, forEmergencyCall
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccConnector.java b/src/java/com/android/internal/telephony/euicc/EuiccConnector.java
index c417a34c54..3c444efdda 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccConnector.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccConnector.java
@@ -47,6 +47,7 @@ import android.service.euicc.IDownloadSubscriptionCallback;
import android.service.euicc.IEraseSubscriptionsCallback;
import android.service.euicc.IEuiccService;
import android.service.euicc.IEuiccServiceDumpResultCallback;
+import android.service.euicc.IGetAvailableMemoryInBytesCallback;
import android.service.euicc.IGetDefaultDownloadableSubscriptionListCallback;
import android.service.euicc.IGetDownloadableSubscriptionMetadataCallback;
import android.service.euicc.IGetEidCallback;
@@ -155,6 +156,7 @@ public class EuiccConnector extends StateMachine implements ServiceConnection {
private static final int CMD_START_OTA_IF_NECESSARY = 112;
private static final int CMD_ERASE_SUBSCRIPTIONS_WITH_OPTIONS = 113;
private static final int CMD_DUMP_EUICC_SERVICE = 114;
+ private static final int CMD_GET_AVAILABLE_MEMORY_IN_BYTES = 115;
private static boolean isEuiccCommand(int what) {
return what >= CMD_GET_EID;
@@ -208,6 +210,13 @@ public class EuiccConnector extends StateMachine implements ServiceConnection {
void onGetEidComplete(String eid);
}
+ /** Callback class for {@link #getAvailableMemoryInBytes}. */
+ @VisibleForTesting(visibility = PACKAGE)
+ public interface GetAvailableMemoryInBytesCommandCallback extends BaseEuiccCommandCallback {
+ /** Called when the available memory in bytes lookup has completed. */
+ void onGetAvailableMemoryInBytesComplete(long availableMemoryInBytes);
+ }
+
/** Callback class for {@link #getOtaStatus}. */
@VisibleForTesting(visibility = PACKAGE)
public interface GetOtaStatusCommandCallback extends BaseEuiccCommandCallback {
@@ -436,6 +445,13 @@ public class EuiccConnector extends StateMachine implements ServiceConnection {
sendMessage(CMD_GET_EID, cardId, 0 /* arg2 */, callback);
}
+ /** Asynchronously fetch the available memory in bytes. */
+ @VisibleForTesting(visibility = PACKAGE)
+ public void getAvailableMemoryInBytes(
+ int cardId, GetAvailableMemoryInBytesCommandCallback callback) {
+ sendMessage(CMD_GET_AVAILABLE_MEMORY_IN_BYTES, cardId, 0 /* arg2 */, callback);
+ }
+
/** Asynchronously get OTA status. */
@VisibleForTesting(visibility = PACKAGE)
public void getOtaStatus(int cardId, GetOtaStatusCommandCallback callback) {
@@ -760,6 +776,22 @@ public class EuiccConnector extends StateMachine implements ServiceConnection {
});
break;
}
+ case CMD_GET_AVAILABLE_MEMORY_IN_BYTES: {
+ mEuiccService.getAvailableMemoryInBytes(slotId,
+ new IGetAvailableMemoryInBytesCallback.Stub() {
+ @Override
+ public void onSuccess(long availableMemoryInBytes) {
+ sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> {
+ ((GetAvailableMemoryInBytesCommandCallback)
+ callback)
+ .onGetAvailableMemoryInBytesComplete(
+ availableMemoryInBytes);
+ onCommandEnd(callback);
+ });
+ }
+ });
+ break;
+ }
case CMD_GET_DOWNLOADABLE_SUBSCRIPTION_METADATA: {
GetMetadataRequest request = (GetMetadataRequest) message.obj;
mEuiccService.getDownloadableSubscriptionMetadata(slotId,
@@ -1036,6 +1068,7 @@ public class EuiccConnector extends StateMachine implements ServiceConnection {
case CMD_GET_OTA_STATUS:
case CMD_START_OTA_IF_NECESSARY:
case CMD_DUMP_EUICC_SERVICE:
+ case CMD_GET_AVAILABLE_MEMORY_IN_BYTES:
return (BaseEuiccCommandCallback) message.obj;
case CMD_GET_DOWNLOADABLE_SUBSCRIPTION_METADATA:
return ((GetMetadataRequest) message.obj).mCallback;
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccController.java b/src/java/com/android/internal/telephony/euicc/EuiccController.java
index a24ab43cef..6dd6f657c8 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccController.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccController.java
@@ -24,6 +24,8 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.flags.Flags;
import android.app.compat.CompatChanges;
import android.content.ComponentName;
import android.content.Context;
@@ -33,6 +35,8 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
import android.service.euicc.DownloadSubscriptionResult;
import android.service.euicc.EuiccService;
@@ -54,6 +58,7 @@ import android.telephony.euicc.EuiccInfo;
import android.telephony.euicc.EuiccManager;
import android.telephony.euicc.EuiccManager.OtaStatus;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.EventLog;
import android.util.Log;
import android.util.Pair;
@@ -72,13 +77,16 @@ import com.android.internal.telephony.uicc.UiccSlot;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
import java.util.Stack;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
/** Backing implementation of {@link android.telephony.euicc.EuiccManager}. */
public class EuiccController extends IEuiccController.Stub {
@@ -121,6 +129,7 @@ public class EuiccController extends IEuiccController.Stub {
// the phone process, 3) values are updated remotely by server flags.
private List<String> mSupportedCountries;
private List<String> mUnsupportedCountries;
+ private List<Integer> mPsimConversionSupportedCarrierIds;
/** Initialize the instance. Should only be called once. */
public static EuiccController init(Context context, FeatureFlags featureFlags) {
@@ -251,6 +260,36 @@ public class EuiccController extends IEuiccController.Stub {
}
/**
+ * Return the available memory in bytes of the eUICC.
+ *
+ * <p>For API simplicity, this call blocks until completion; while it requires an IPC to load,
+ * that IPC should generally be fast, and the available memory shouldn't be needed in the normal
+ * course of operation.
+ */
+ @Override
+ public long getAvailableMemoryInBytes(int cardId, String callingPackage) {
+ boolean callerCanReadPhoneStatePrivileged = callerCanReadPhoneStatePrivileged();
+ boolean callerCanReadPhoneState = callerCanReadPhoneState();
+ mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
+ long token = Binder.clearCallingIdentity();
+ try {
+ if (!callerCanReadPhoneStatePrivileged
+ && !callerCanReadPhoneState
+ && !canManageSubscriptionOnTargetSim(
+ cardId, callingPackage, false, TelephonyManager.INVALID_PORT_INDEX)) {
+ throw new SecurityException(
+ "Must have READ_PHONE_STATE permission or READ_PRIVILEGED_PHONE_STATE"
+ + " permission or carrier privileges to read the available memory for"
+ + "cardId="
+ + cardId);
+ }
+ return blockingGetAvailableMemoryInBytesFromEuiccService(cardId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
* Return the current status of OTA update.
*
* <p>For API simplicity, this call blocks until completion; while it requires an IPC to load,
@@ -577,6 +616,19 @@ public class EuiccController extends IEuiccController.Stub {
boolean switchAfterDownload, String callingPackage, boolean forceDeactivateSim,
Bundle resolvedBundle, PendingIntent callbackIntent) {
boolean callerCanWriteEmbeddedSubscriptions = callerCanWriteEmbeddedSubscriptions();
+ boolean callerCanDownloadAdminManagedSubscription =
+ Flags.esimManagementEnabled()
+ && callerCanManageDevicePolicyManagedSubscriptions(callingPackage);
+ if (Flags.esimManagementEnabled()) {
+ if (mContext
+ .getSystemService(UserManager.class)
+ .hasUserRestriction(UserManager.DISALLOW_SIM_GLOBALLY)
+ && !callerCanDownloadAdminManagedSubscription) {
+ // Only admin managed subscriptions are allowed, but the caller is not authorised to
+ // download admin managed subscriptions. Abort.
+ throw new SecurityException("Caller is not authorized to download subscriptions");
+ }
+ }
mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
// Don't try to resolve the port index for apps which are not targeting on T for backward
// compatibility. instead always use default port 0.
@@ -594,18 +646,27 @@ public class EuiccController extends IEuiccController.Stub {
isConsentNeededToResolvePortIndex = (portIndex
== TelephonyManager.INVALID_PORT_INDEX);
}
+ // Caller has admin privileges if they can download admin managed subscription,
+ // and are not switching the subscription after download (admins cannot silently
+ // enable the subscription).
+ boolean hasAdminPrivileges =
+ callerCanDownloadAdminManagedSubscription && !switchAfterDownload;
Log.d(TAG, " downloadSubscription cardId: " + cardId + " switchAfterDownload: "
+ switchAfterDownload + " portIndex: " + portIndex
+ " forceDeactivateSim: " + forceDeactivateSim + " callingPackage: "
+ callingPackage
+ " isConsentNeededToResolvePortIndex: " + isConsentNeededToResolvePortIndex
- + " shouldResolvePortIndex:" + shouldResolvePortIndex);
- if (!isConsentNeededToResolvePortIndex && callerCanWriteEmbeddedSubscriptions) {
+ + " shouldResolvePortIndex:" + shouldResolvePortIndex
+ + " hasAdminPrivileges:" + hasAdminPrivileges);
+ if (!isConsentNeededToResolvePortIndex
+ && (callerCanWriteEmbeddedSubscriptions
+ || hasAdminPrivileges)) {
// With WRITE_EMBEDDED_SUBSCRIPTIONS, we can skip profile-specific permission checks
// and move straight to the profile download.
downloadSubscriptionPrivileged(cardId, portIndex, token, subscription,
switchAfterDownload, forceDeactivateSim, callingPackage, resolvedBundle,
- callbackIntent);
+ callbackIntent, callerCanDownloadAdminManagedSubscription,
+ getCurrentEmbeddedSubscriptionIds(cardId));
return;
}
@@ -741,11 +802,30 @@ public class EuiccController extends IEuiccController.Stub {
true /* withUserConsent */, portIndex));
}
+ void downloadSubscriptionPrivileged(int cardId, int portIndex, final long callingToken,
+ DownloadableSubscription subscription, boolean switchAfterDownload,
+ boolean forceDeactivateSim, final String callingPackage,
+ Bundle resolvedBundle, final PendingIntent callbackIntent) {
+ downloadSubscriptionPrivileged(
+ cardId,
+ portIndex,
+ callingToken,
+ subscription,
+ switchAfterDownload,
+ forceDeactivateSim,
+ callingPackage,
+ resolvedBundle,
+ callbackIntent,
+ false /* markAsOwnedByAdmin */,
+ new ArraySet<>() /* existingSubscriptions */);
+ }
+
// Continue to download subscription without checking anything.
void downloadSubscriptionPrivileged(int cardId, int portIndex, final long callingToken,
DownloadableSubscription subscription, boolean switchAfterDownload,
boolean forceDeactivateSim, final String callingPackage, Bundle resolvedBundle,
- final PendingIntent callbackIntent) {
+ final PendingIntent callbackIntent, boolean markAsOwnedByAdmin,
+ Set<Integer> existingSubscriptions) {
mConnector.downloadSubscription(
cardId,
portIndex,
@@ -774,7 +854,13 @@ public class EuiccController extends IEuiccController.Stub {
// Since we're not switching, nothing will trigger a
// subscription list refresh on its own, so request one here.
refreshSubscriptionsAndSendResult(
- callbackIntent, resultCode, extrasIntent);
+ callbackIntent,
+ resultCode,
+ extrasIntent,
+ markAsOwnedByAdmin,
+ callingPackage,
+ cardId,
+ existingSubscriptions);
return;
}
break;
@@ -976,6 +1062,9 @@ public class EuiccController extends IEuiccController.Stub {
public void deleteSubscription(int cardId, int subscriptionId, String callingPackage,
PendingIntent callbackIntent) {
boolean callerCanWriteEmbeddedSubscriptions = callerCanWriteEmbeddedSubscriptions();
+ boolean callerIsAdmin =
+ Flags.esimManagementEnabled()
+ && callerCanManageDevicePolicyManagedSubscriptions(callingPackage);
mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
long token = Binder.clearCallingIdentity();
@@ -986,13 +1075,14 @@ public class EuiccController extends IEuiccController.Stub {
sendResult(callbackIntent, ERROR, null /* extrasIntent */);
return;
}
-
+ boolean adminOwned = callerIsAdmin && sub.getGroupOwner().equals(callingPackage);
// For both single active SIM device and multi-active SIM device, if the caller is
// system or the caller manage the target subscription, we let it continue. This is
// because deleting subscription won't change status of any other subscriptions.
if (!callerCanWriteEmbeddedSubscriptions
- && !mSubscriptionManager.canManageSubscription(sub, callingPackage)) {
- Log.e(TAG, "No permissions: " + subscriptionId);
+ && !mSubscriptionManager.canManageSubscription(sub, callingPackage)
+ && !adminOwned) {
+ Log.e(TAG, "No permissions: " + subscriptionId + " adminOwned=" + adminOwned);
sendResult(callbackIntent, ERROR, null /* extrasIntent */);
return;
}
@@ -1610,9 +1700,43 @@ public class EuiccController extends IEuiccController.Stub {
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
public void refreshSubscriptionsAndSendResult(
PendingIntent callbackIntent, int resultCode, Intent extrasIntent) {
+ refreshSubscriptionsAndSendResult(
+ callbackIntent,
+ resultCode,
+ extrasIntent,
+ /* isCallerAdmin= */ false,
+ /* callingPackage= */ "",
+ /* cardId */ -1,
+ /* subscriptionsBefore= */ new ArraySet<>());
+ }
+
+ /** Refresh the embedded subscription list and dispatch the given result upon completion. */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public void refreshSubscriptionsAndSendResult(
+ PendingIntent callbackIntent,
+ int resultCode,
+ Intent extrasIntent,
+ boolean isCallerAdmin,
+ String callingPackage,
+ int cardId,
+ Set<Integer> subscriptionsBefore) {
SubscriptionManagerService.getInstance().updateEmbeddedSubscriptions(
List.of(mTelephonyManager.getCardIdForDefaultEuicc()),
- () -> sendResult(callbackIntent, resultCode, extrasIntent));
+ () -> {
+ if (Flags.esimManagementEnabled() && isCallerAdmin) {
+ // Mark the newly downloaded subscriptions as being owned by an admin so
+ // that actions for that subscription can be restricted,
+ // and the admin is limited to effecting only these subscriptions.
+ Set<Integer> subscriptionsAfter = getCurrentEmbeddedSubscriptionIds(cardId);
+ subscriptionsAfter.removeAll(subscriptionsBefore);
+ for (int subId: subscriptionsAfter) {
+ SubscriptionManagerService
+ .getInstance().setGroupOwner(subId, callingPackage);
+ }
+ }
+ sendResult(callbackIntent, resultCode, extrasIntent);
+ });
+
}
/** Dispatch the given callback intent with the given result code and data. */
@@ -1728,6 +1852,23 @@ public class EuiccController extends IEuiccController.Stub {
return null;
}
+ private Set<Integer> getCurrentEmbeddedSubscriptionIds(int cardId) {
+ if (!Flags.esimManagementEnabled()) {
+ return new ArraySet<>();
+ }
+ List<SubscriptionInfo> subscriptionInfos =
+ mSubscriptionManager.getAvailableSubscriptionInfoList();
+ int subCount = (subscriptionInfos != null) ? subscriptionInfos.size() : 0;
+ Set<Integer> currentEmbeddedSubscriptionIds = new ArraySet<>();
+ for (int i = 0; i < subCount; i++) {
+ SubscriptionInfo subscriptionInfo = subscriptionInfos.get(i);
+ if (subscriptionInfo.isEmbedded() && subscriptionInfo.getCardId() == cardId) {
+ currentEmbeddedSubscriptionIds.add(subscriptionInfo.getSubscriptionId());
+ }
+ }
+ return currentEmbeddedSubscriptionIds;
+ }
+
@Nullable
private String blockingGetEidFromEuiccService(int cardId) {
CountDownLatch latch = new CountDownLatch(1);
@@ -1747,6 +1888,27 @@ public class EuiccController extends IEuiccController.Stub {
return awaitResult(latch, eidRef);
}
+ private long blockingGetAvailableMemoryInBytesFromEuiccService(int cardId) {
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicReference<Long> memoryRef =
+ new AtomicReference<>(EuiccManager.EUICC_MEMORY_FIELD_UNAVAILABLE);
+ mConnector.getAvailableMemoryInBytes(
+ cardId,
+ new EuiccConnector.GetAvailableMemoryInBytesCommandCallback() {
+ @Override
+ public void onGetAvailableMemoryInBytesComplete(long availableMemoryInBytes) {
+ memoryRef.set(availableMemoryInBytes);
+ latch.countDown();
+ }
+
+ @Override
+ public void onEuiccServiceUnavailable() {
+ latch.countDown();
+ }
+ });
+ return awaitResult(latch, memoryRef);
+ }
+
private @OtaStatus int blockingGetOtaStatusFromEuiccService(int cardId) {
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<Integer> statusRef =
@@ -1954,12 +2116,49 @@ public class EuiccController extends IEuiccController.Stub {
== PackageManager.PERMISSION_GRANTED;
}
+ private boolean callerCanReadPhoneState() {
+ return mContext.checkCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
private boolean callerCanWriteEmbeddedSubscriptions() {
return mContext.checkCallingOrSelfPermission(
Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
== PackageManager.PERMISSION_GRANTED;
}
+ private DevicePolicyManager retrieveDevicePolicyManagerFromUserContext(UserHandle userHandle) {
+ Context userContext;
+ long ident = Binder.clearCallingIdentity();
+ try {
+ userContext = mContext.createPackageContextAsUser(
+ mContext.getPackageName(), /* flags= */ 0, userHandle);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Unknown package name");
+ return null;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ return userContext.getSystemService(DevicePolicyManager.class);
+ }
+
+ private boolean callerCanManageDevicePolicyManagedSubscriptions(String callingPackage) {
+ // isProfileOwner/isDeviceOwner needs to callers user, so create device policy manager
+ // with the correct context associated with the caller.
+ DevicePolicyManager devicePolicyManager =
+ retrieveDevicePolicyManagerFromUserContext(Binder.getCallingUserHandle());
+ if (devicePolicyManager == null) {
+ Log.w(TAG, "Unable to get device policy manager");
+ return false;
+ }
+ boolean isAdmin =
+ devicePolicyManager.isProfileOwnerApp(callingPackage)
+ || devicePolicyManager.isDeviceOwnerApp(callingPackage);
+ return isAdmin || mContext.checkCallingOrSelfPermission(
+ Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
@Override
public boolean isSimPortAvailable(int cardId, int portIndex, String callingPackage) {
mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
@@ -2073,6 +2272,34 @@ public class EuiccController extends IEuiccController.Stub {
return changeEnabled;
}
+
+ @Override
+ public void setPsimConversionSupportedCarriers(int[] carrierIds) {
+ if (!callerCanWriteEmbeddedSubscriptions()) {
+ throw new SecurityException(
+ "Must have WRITE_EMBEDDED_SUBSCRIPTIONS to "
+ + "set pSIM conversion supported carriers");
+ }
+ mPsimConversionSupportedCarrierIds = Arrays.stream(carrierIds).boxed()
+ .collect(Collectors.toList());
+ }
+
+
+
+ @Override
+ public boolean isPsimConversionSupported(int carrierId) {
+ if (!callerCanWriteEmbeddedSubscriptions()) {
+ throw new SecurityException(
+ "Must have WRITE_EMBEDDED_SUBSCRIPTIONS "
+ + "to check if the carrier is supported pSIM conversion");
+ }
+ if (mPsimConversionSupportedCarrierIds == null
+ || mPsimConversionSupportedCarrierIds.isEmpty()) {
+ return false;
+ }
+ return mPsimConversionSupportedCarrierIds.contains(carrierId);
+ }
+
/**
* Make sure the device has required telephony feature
*
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsNrSaModeHandler.java b/src/java/com/android/internal/telephony/imsphone/ImsNrSaModeHandler.java
index 6554077e9b..234723fffb 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsNrSaModeHandler.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsNrSaModeHandler.java
@@ -263,9 +263,10 @@ public class ImsNrSaModeHandler extends Handler{
PersistableBundle bundle = mCarrierConfigManager.getConfigForSubId(mPhone.getSubId(),
KEY_NR_SA_DISABLE_POLICY_INT, KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY);
mNrSaDisablePolicy = bundle.getInt(KEY_NR_SA_DISABLE_POLICY_INT);
- mIsNrSaSupported = Arrays.stream(
- bundle.getIntArray(KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY)).anyMatch(
- value -> value == CARRIER_NR_AVAILABILITY_SA);
+ int[] nrAvailabilities = bundle.getIntArray(KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY);
+ mIsNrSaSupported = nrAvailabilities != null
+ && Arrays.stream(nrAvailabilities).anyMatch(
+ value -> value == CARRIER_NR_AVAILABILITY_SA);
Log.d(TAG, "setNrSaDisablePolicy : NrSaDisablePolicy = "
+ mNrSaDisablePolicy + ", IsNrSaSupported = " + mIsNrSaSupported);
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
index 9f3ec3bf44..f4f632e963 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -21,10 +21,10 @@ import static android.telephony.ims.ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_TI
import static android.telephony.ims.RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED;
import static android.telephony.ims.RegistrationManager.REGISTRATION_STATE_REGISTERED;
import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_NONE;
+import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCKS;
import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK;
import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT;
import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_TRIGGER_RAT_BLOCK;
-import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCKS;
import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NONE;
import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BAIC;
@@ -49,7 +49,6 @@ import static com.android.internal.telephony.CommandsInterface.CF_REASON_UNCONDI
import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_NONE;
import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_VOICE;
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.app.Activity;
import android.app.Notification;
@@ -124,7 +123,6 @@ import com.android.internal.telephony.domainselection.DomainSelectionResolver;
import com.android.internal.telephony.emergency.EmergencyNumberTracker;
import com.android.internal.telephony.emergency.EmergencyStateTracker;
import com.android.internal.telephony.flags.FeatureFlags;
-import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.gsm.SuppServiceNotification;
import com.android.internal.telephony.metrics.ImsStats;
import com.android.internal.telephony.metrics.TelephonyMetrics;
@@ -2561,7 +2559,6 @@ public class ImsPhone extends ImsPhoneBase {
/** Clear the IMS phone number from IMS associated Uris when IMS registration is lost. */
@VisibleForTesting
- @FlaggedApi(Flags.FLAG_CLEAR_CACHED_IMS_PHONE_NUMBER_WHEN_DEVICE_LOST_IMS_REGISTRATION)
public void clearPhoneNumberForSourceIms() {
int subId = getSubId();
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
@@ -2832,6 +2829,15 @@ public class ImsPhone extends ImsPhoneBase {
mCT.triggerNotifyAnbr(mediaType, direction, bitsPerSecond);
}
+ /**
+ * Check whether making a call using Wi-Fi is possible or not.
+ * @return {code true} if IMS is registered over IWLAN else return {code false}.
+ */
+ public boolean canMakeWifiCall() {
+ return isImsRegistered() && (getImsRegistrationTech()
+ == ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN);
+ }
+
@Override
public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
index e95433c2ee..b5a052d518 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -1259,8 +1259,6 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
}
}
- private @NonNull final FeatureFlags mFeatureFlags;
-
//***** Events
@@ -1273,8 +1271,9 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
@VisibleForTesting
public ImsPhoneCallTracker(ImsPhone phone, ConnectorFactory factory, Executor executor,
FeatureFlags featureFlags) {
+ super(featureFlags);
+
this.mPhone = phone;
- mFeatureFlags = featureFlags;
mConnectorFactory = factory;
if (executor != null) {
mExecutor = executor;
@@ -4453,13 +4452,15 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
public void onCallQualityChanged(ImsCall imsCall, CallQuality callQuality) {
// convert ServiceState.radioTech to TelephonyManager.NetworkType constant
mPhone.onCallQualityChanged(callQuality, imsCall.getNetworkType());
- String callId = imsCall.getSession().getCallId();
- CallQualityMetrics cqm = mCallQualityMetrics.get(callId);
- if (cqm == null) {
- cqm = new CallQualityMetrics(mPhone);
+ if (imsCall.getSession() != null) {
+ String callId = imsCall.getSession().getCallId();
+ CallQualityMetrics cqm = mCallQualityMetrics.get(callId);
+ if (cqm == null) {
+ cqm = new CallQualityMetrics(mPhone);
+ }
+ cqm.saveCallQuality(callQuality);
+ mCallQualityMetrics.put(callId, cqm);
}
- cqm.saveCallQuality(callQuality);
- mCallQualityMetrics.put(callId, cqm);
ImsPhoneConnection conn = findConnection(imsCall);
if (conn != null) {
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
index 104c925759..a71355d5bd 100755
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
@@ -1357,17 +1357,15 @@ public class ImsPhoneConnection extends Connection implements
* @param imsCall The call to check for changes in extras.
* @return Whether the extras fields have been changed.
*/
- boolean updateExtras(ImsCall imsCall) {
+ boolean updateExtras(ImsCall imsCall) {
if (imsCall == null) {
return false;
}
-
final ImsCallProfile callProfile = imsCall.getCallProfile();
final Bundle extras = callProfile != null ? callProfile.mCallExtras : null;
if (extras == null && DBG) {
Rlog.d(LOG_TAG, "Call profile extras are null.");
}
-
final boolean changed = !areBundlesEqual(extras, mExtras);
if (changed) {
updateImsCallRatFromExtras(extras);
@@ -1377,11 +1375,44 @@ public class ImsPhoneConnection extends Connection implements
if (extras != null) {
mExtras.putAll(extras);
}
+ if (com.android.server.telecom.flags.Flags.businessCallComposer()) {
+ maybeInjectBusinessComposerExtras(mExtras);
+ }
setConnectionExtras(mExtras);
}
return changed;
}
+ /**
+ * The Ims Vendor is responsible for setting the ImsCallProfile business call composer
+ * values (ImsCallProfile.EXTRA_IS_BUSINESS_CALL and
+ * ImsCallProfile.EXTRA_ASSERTED_DISPLAY_NAME). This helper notifies Telecom of the business
+ * composer values which will then be injected into the android.telecom.Call object.
+ */
+ private void maybeInjectBusinessComposerExtras(Bundle extras) {
+ if (extras == null) {
+ return;
+ }
+ try {
+ if (extras.containsKey(ImsCallProfile.EXTRA_IS_BUSINESS_CALL)
+ && !extras.containsKey(android.telecom.Call.EXTRA_IS_BUSINESS_CALL)) {
+ boolean v = extras.getBoolean(ImsCallProfile.EXTRA_IS_BUSINESS_CALL);
+ Rlog.i(LOG_TAG, String.format("mIBCE: EXTRA_IS_BUSINESS_CALL=[%s]", v));
+ extras.putBoolean(android.telecom.Call.EXTRA_IS_BUSINESS_CALL, v);
+ }
+
+ if (extras.containsKey(ImsCallProfile.EXTRA_ASSERTED_DISPLAY_NAME)
+ && !extras.containsKey(android.telecom.Call.EXTRA_ASSERTED_DISPLAY_NAME)) {
+ String v = extras.getString(ImsCallProfile.EXTRA_ASSERTED_DISPLAY_NAME);
+ Rlog.i(LOG_TAG, String.format("mIBCE: ASSERTED_DISPLAY_NAME=[%s]", v));
+ extras.putString(android.telecom.Call.EXTRA_ASSERTED_DISPLAY_NAME, v);
+ }
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
private static boolean areBundlesEqual(Bundle extras, Bundle newExtras) {
if (extras == null || newExtras == null) {
return extras == newExtras;
diff --git a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
index cc80a2ec72..a315f1ed7c 100644
--- a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
+++ b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
@@ -1125,7 +1125,7 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback {
roundAndConvertMillisToSeconds(stats.registeringMillis),
roundAndConvertMillisToSeconds(stats.unregisteredMillis),
stats.isIwlanCrossSim,
- roundAndConvertMillisToSeconds(stats.registeredTimes));
+ stats.registeredTimes);
}
private static StatsEvent buildStatsEvent(ImsRegistrationTermination termination) {
diff --git a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
index 4a1fdb9412..f3fe8fa333 100644
--- a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
+++ b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
@@ -2291,8 +2291,6 @@ public class PersistAtomsStorage {
normalizeDurationTo24H(stats[i].registeringMillis, intervalMillis);
stats[i].unregisteredMillis =
normalizeDurationTo24H(stats[i].unregisteredMillis, intervalMillis);
- stats[i].registeredTimes =
- normalizeDurationTo24H(stats[i].registeredTimes, intervalMillis);
}
return stats;
}
diff --git a/src/java/com/android/internal/telephony/satellite/DatagramController.java b/src/java/com/android/internal/telephony/satellite/DatagramController.java
index 0c68d4b328..877eaf115e 100644
--- a/src/java/com/android/internal/telephony/satellite/DatagramController.java
+++ b/src/java/com/android/internal/telephony/satellite/DatagramController.java
@@ -18,6 +18,8 @@ package com.android.internal.telephony.satellite;
import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED;
import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_IDLE;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_OFF;
import android.annotation.NonNull;
import android.content.Context;
@@ -29,6 +31,7 @@ import android.telephony.satellite.ISatelliteDatagramCallback;
import android.telephony.satellite.SatelliteDatagram;
import android.telephony.satellite.SatelliteManager;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -50,8 +53,12 @@ public class DatagramController {
public static final long MAX_DATAGRAM_ID = (long) Math.pow(2, 16);
public static final int ROUNDING_UNIT = 10;
public static final long SATELLITE_ALIGN_TIMEOUT = TimeUnit.SECONDS.toMillis(30);
- public static final long DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT =
- TimeUnit.SECONDS.toMillis(60);
+ /** This type is used by CTS to override the satellite align timeout */
+ public static final int TIMEOUT_TYPE_ALIGN = 1;
+ /** This type is used by CTS to override the time to wait for connected state */
+ public static final int TIMEOUT_TYPE_DATAGRAM_WAIT_FOR_CONNECTED_STATE = 2;
+ /** This type is used by CTS to override the time to wait for response of the send request */
+ public static final int TIMEOUT_TYPE_WAIT_FOR_DATAGRAM_SENDING_RESPONSE = 3;
private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem";
private static final boolean DEBUG = !"user".equals(Build.TYPE);
@@ -80,7 +87,8 @@ public class DatagramController {
private SatelliteDatagram mDemoModeDatagram;
private boolean mIsDemoMode = false;
private long mAlignTimeoutDuration = SATELLITE_ALIGN_TIMEOUT;
- private long mDatagramWaitTimeForConnectedState = DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT;
+ private long mDatagramWaitTimeForConnectedState;
+ private long mModemImageSwitchingDuration;
@GuardedBy("mLock")
@SatelliteManager.SatelliteModemState
private int mSatelltieModemState = SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN;
@@ -132,6 +140,9 @@ public class DatagramController {
// Create the DatagramReceiver singleton,
// which is used to receive satellite datagrams.
mDatagramReceiver = DatagramReceiver.make(mContext, looper, this);
+
+ mDatagramWaitTimeForConnectedState = getDatagramWaitForConnectedStateTimeoutMillis();
+ mModemImageSwitchingDuration = getSatelliteModemImageSwitchingDurationMillis();
}
/**
@@ -367,25 +378,51 @@ public class DatagramController {
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public long getDatagramWaitTimeForConnectedState() {
- return mDatagramWaitTimeForConnectedState;
+ synchronized (mLock) {
+ if (mSatelltieModemState == SATELLITE_MODEM_STATE_OFF
+ || mSatelltieModemState == SATELLITE_MODEM_STATE_IDLE) {
+ return (mDatagramWaitTimeForConnectedState + mModemImageSwitchingDuration);
+ }
+ return mDatagramWaitTimeForConnectedState;
+ }
}
/**
- * This API can be used by only CTS to update the timeout duration in milliseconds whether
- * the device is aligned with the satellite for demo mode
+ * This API can be used by only CTS to timeout durations used by DatagramController module.
*
* @param timeoutMillis The timeout duration in millisecond.
* @return {@code true} if the timeout duration is set successfully, {@code false} otherwise.
*/
- boolean setSatelliteDeviceAlignedTimeoutDuration(long timeoutMillis) {
+ boolean setDatagramControllerTimeoutDuration(
+ boolean reset, int timeoutType, long timeoutMillis) {
if (!isMockModemAllowed()) {
- loge("Updating align timeout duration is not allowed");
+ loge("Updating timeout duration is not allowed");
return false;
}
- logd("setSatelliteDeviceAlignedTimeoutDuration: timeoutMillis=" + timeoutMillis);
- mAlignTimeoutDuration = timeoutMillis;
- mDatagramWaitTimeForConnectedState = timeoutMillis;
+ logd("setDatagramControllerTimeoutDuration: timeoutMillis=" + timeoutMillis
+ + ", reset=" + reset + ", timeoutType=" + timeoutType);
+ if (timeoutType == TIMEOUT_TYPE_ALIGN) {
+ if (reset) {
+ mAlignTimeoutDuration = SATELLITE_ALIGN_TIMEOUT;
+ } else {
+ mAlignTimeoutDuration = timeoutMillis;
+ }
+ } else if (timeoutType == TIMEOUT_TYPE_DATAGRAM_WAIT_FOR_CONNECTED_STATE) {
+ if (reset) {
+ mDatagramWaitTimeForConnectedState =
+ getDatagramWaitForConnectedStateTimeoutMillis();
+ mModemImageSwitchingDuration = getSatelliteModemImageSwitchingDurationMillis();
+ } else {
+ mDatagramWaitTimeForConnectedState = timeoutMillis;
+ mModemImageSwitchingDuration = 0;
+ }
+ } else if (timeoutType == TIMEOUT_TYPE_WAIT_FOR_DATAGRAM_SENDING_RESPONSE) {
+ mDatagramDispatcher.setWaitTimeForDatagramSendingResponse(reset, timeoutMillis);
+ } else {
+ loge("Invalid timeout type " + timeoutType);
+ return false;
+ }
return true;
}
@@ -404,6 +441,16 @@ public class DatagramController {
}
}
+ private long getDatagramWaitForConnectedStateTimeoutMillis() {
+ return mContext.getResources().getInteger(
+ R.integer.config_datagram_wait_for_connected_state_timeout_millis);
+ }
+
+ private long getSatelliteModemImageSwitchingDurationMillis() {
+ return mContext.getResources().getInteger(
+ R.integer.config_satellite_modem_image_switching_duration_millis);
+ }
+
/**
* This API can be used by only CTS to override the cached value for the device overlay config
* value : config_send_satellite_datagram_to_modem_in_demo_mode, which determines whether
diff --git a/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java b/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
index ae4c1f29e0..5cac1dd71c 100644
--- a/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
+++ b/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
@@ -17,6 +17,7 @@
package com.android.internal.telephony.satellite;
import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODEM_TIMEOUT;
import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
import static com.android.internal.telephony.satellite.DatagramController.ROUNDING_UNIT;
@@ -58,6 +59,8 @@ public class DatagramDispatcher extends Handler {
private static final int EVENT_SEND_SATELLITE_DATAGRAM_DONE = 2;
private static final int EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT = 3;
private static final int EVENT_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMED_OUT = 4;
+ private static final int EVENT_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_TIMED_OUT = 5;
+ private static final int EVENT_ABORT_SENDING_SATELLITE_DATAGRAMS_DONE = 6;
@NonNull private static DatagramDispatcher sInstance;
@NonNull private final Context mContext;
@@ -93,6 +96,8 @@ public class DatagramDispatcher extends Handler {
private final LinkedHashMap<Long, SendSatelliteDatagramArgument>
mPendingNonEmergencyDatagramsMap = new LinkedHashMap<>();
+ private long mWaitTimeForDatagramSendingResponse;
+
/**
* Create the DatagramDispatcher singleton instance.
* @param context The Context to use to create the DatagramDispatcher.
@@ -126,6 +131,7 @@ public class DatagramDispatcher extends Handler {
synchronized (mLock) {
mSendingDatagramInProgress = false;
}
+ mWaitTimeForDatagramSendingResponse = getWaitForDatagramSendingResponseTimeoutMillis();
}
private static final class DatagramDispatcherHandlerRequest {
@@ -196,13 +202,17 @@ public class DatagramDispatcher extends Handler {
(SendSatelliteDatagramArgument) request.argument;
onCompleted = obtainMessage(EVENT_SEND_SATELLITE_DATAGRAM_DONE, request);
- if (mIsDemoMode && !shouldSendDatagramToModemInDemoMode()) {
- AsyncResult.forMessage(onCompleted, SATELLITE_RESULT_SUCCESS, null);
- onCompleted.sendToTarget();
- } else {
- SatelliteModemInterface.getInstance().sendSatelliteDatagram(argument.datagram,
- argument.datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
- argument.needFullScreenPointingUI, onCompleted);
+ synchronized (mLock) {
+ if (mIsDemoMode && !shouldSendDatagramToModemInDemoMode()) {
+ AsyncResult.forMessage(onCompleted, SATELLITE_RESULT_SUCCESS, null);
+ onCompleted.sendToTarget();
+ } else {
+ SatelliteModemInterface.getInstance().sendSatelliteDatagram(
+ argument.datagram,
+ argument.datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ argument.needFullScreenPointingUI, onCompleted);
+ startWaitForDatagramSendingResponseTimer(argument);
+ }
}
break;
}
@@ -223,13 +233,25 @@ public class DatagramDispatcher extends Handler {
break;
}
}
-
logd("EVENT_SEND_SATELLITE_DATAGRAM_DONE error: " + error);
- // log metrics about the outgoing datagram
- reportSendDatagramCompleted(argument, error);
+ /*
+ * The response should be ignored if either of the following hold
+ * 1) Framework has already received this response from the vendor service.
+ * 2) Framework has timed out to wait for the response from vendor service for
+ * the send request.
+ * 3) All pending send requests have been aborted due to some error.
+ */
+ if (!shouldProcessEventSendSatelliteDatagramDone(argument)) {
+ logw("The message " + argument.datagramId + " was already processed");
+ break;
+ }
+
+ stopWaitForDatagramSendingResponseTimer();
mSendingDatagramInProgress = false;
+ // Log metrics about the outgoing datagram
+ reportSendDatagramCompleted(argument, error);
// Remove current datagram from pending map.
if (argument.datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) {
mPendingEmergencyDatagramsMap.remove(argument.datagramId);
@@ -278,6 +300,11 @@ public class DatagramDispatcher extends Handler {
break;
}
+ case EVENT_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_TIMED_OUT:
+ handleEventWaitForDatagramSendingResponseTimedOut(
+ (SendSatelliteDatagramArgument) msg.obj);
+ break;
+
case EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT: {
handleEventSatelliteAlignedTimeout((DatagramDispatcherHandlerRequest) msg.obj);
break;
@@ -596,6 +623,7 @@ public class DatagramDispatcher extends Handler {
stopSatelliteAlignedTimer();
stopDatagramWaitForConnectedStateTimer();
+ stopWaitForDatagramSendingResponseTimer();
mIsDemoMode = false;
mSendSatelliteDatagramRequest = null;
mIsAligned = false;
@@ -620,6 +648,32 @@ public class DatagramDispatcher extends Handler {
return hasMessages(EVENT_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMED_OUT);
}
+ /**
+ * This API is used by CTS tests to override the mWaitTimeForDatagramSendingResponse.
+ */
+ void setWaitTimeForDatagramSendingResponse(boolean reset, long timeoutMillis) {
+ if (reset) {
+ mWaitTimeForDatagramSendingResponse = getWaitForDatagramSendingResponseTimeoutMillis();
+ } else {
+ mWaitTimeForDatagramSendingResponse = timeoutMillis;
+ }
+ }
+
+ private void startWaitForDatagramSendingResponseTimer(
+ @NonNull SendSatelliteDatagramArgument argument) {
+ if (hasMessages(EVENT_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_TIMED_OUT)) {
+ logd("WaitForDatagramSendingResponseTimer was already started");
+ return;
+ }
+ sendMessageDelayed(obtainMessage(
+ EVENT_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_TIMED_OUT, argument),
+ mWaitTimeForDatagramSendingResponse);
+ }
+
+ private void stopWaitForDatagramSendingResponseTimer() {
+ removeMessages(EVENT_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_TIMED_OUT);
+ }
+
private void handleEventDatagramWaitForConnectedStateTimedOut() {
logw("Timed out to wait for satellite connected before sending datagrams");
synchronized (mLock) {
@@ -654,6 +708,61 @@ public class DatagramDispatcher extends Handler {
}
}
+ private long getWaitForDatagramSendingResponseTimeoutMillis() {
+ return mContext.getResources().getInteger(
+ R.integer.config_wait_for_datagram_sending_response_timeout_millis);
+ }
+
+ private boolean shouldProcessEventSendSatelliteDatagramDone(
+ @NonNull SendSatelliteDatagramArgument argument) {
+ synchronized (mLock) {
+ if (argument.datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) {
+ return mPendingEmergencyDatagramsMap.containsKey(argument.datagramId);
+ } else {
+ return mPendingNonEmergencyDatagramsMap.containsKey(argument.datagramId);
+ }
+ }
+ }
+
+ private void handleEventWaitForDatagramSendingResponseTimedOut(
+ @NonNull SendSatelliteDatagramArgument argument) {
+ synchronized (mLock) {
+ logw("Timed out to wait for the response of the request to send the datagram "
+ + argument.datagramId);
+
+ // Ask vendor service to abort all datagram-sending requests
+ SatelliteModemInterface.getInstance().abortSendingSatelliteDatagrams(
+ obtainMessage(EVENT_ABORT_SENDING_SATELLITE_DATAGRAMS_DONE, argument));
+ mSendingDatagramInProgress = false;
+
+ // Update send status
+ mDatagramController.updateSendStatus(argument.subId,
+ SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED,
+ getPendingDatagramCount(), SATELLITE_RESULT_MODEM_TIMEOUT);
+ mDatagramController.updateSendStatus(argument.subId,
+ SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+ 0, SatelliteManager.SATELLITE_RESULT_SUCCESS);
+
+ // Send response for current datagram after updating datagram transfer state
+ // internally.
+ argument.callback.accept(SATELLITE_RESULT_MODEM_TIMEOUT);
+
+ // Log metrics about the outgoing datagram
+ reportSendDatagramCompleted(argument, SATELLITE_RESULT_MODEM_TIMEOUT);
+ mControllerMetricsStats.reportOutgoingDatagramFailCount(argument.datagramType);
+ // Remove current datagram from pending map.
+ if (argument.datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) {
+ mPendingEmergencyDatagramsMap.remove(argument.datagramId);
+ } else {
+ mPendingNonEmergencyDatagramsMap.remove(argument.datagramId);
+ }
+
+ // Abort sending all the pending datagrams
+ abortSendingPendingDatagrams(argument.subId,
+ SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED);
+ }
+ }
+
/**
* This API can be used by only CTS to override the cached value for the device overlay config
* value : config_send_satellite_datagram_to_modem_in_demo_mode, which determines whether
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteController.java b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
index aaabaff26e..0d3973d127 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
@@ -16,10 +16,13 @@
package com.android.internal.telephony.satellite;
+import static android.provider.Settings.ACTION_SATELLITE_SETTING;
import static android.telephony.CarrierConfigManager.KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE;
import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL;
import static android.telephony.CarrierConfigManager.KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT;
+import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL;
import static android.telephony.SubscriptionManager.SATELLITE_ATTACH_ENABLED_FOR_CARRIER;
+import static android.telephony.SubscriptionManager.SATELLITE_ENTITLEMENT_STATUS;
import static android.telephony.SubscriptionManager.isValidSubscriptionId;
import static android.telephony.satellite.NtnSignalStrength.NTN_SIGNAL_STRENGTH_NONE;
import static android.telephony.satellite.SatelliteManager.EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS;
@@ -27,12 +30,17 @@ import static android.telephony.satellite.SatelliteManager.EMERGENCY_CALL_TO_SAT
import static android.telephony.satellite.SatelliteManager.KEY_NTN_SIGNAL_STRENGTH;
import static android.telephony.satellite.SatelliteManager.SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT;
import static android.telephony.satellite.SatelliteManager.SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODEM_TIMEOUT;
import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED;
import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
import android.annotation.ArrayRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -42,6 +50,7 @@ import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.database.ContentObserver;
+import android.net.Uri;
import android.net.wifi.WifiManager;
import android.nfc.NfcAdapter;
import android.os.AsyncResult;
@@ -62,6 +71,7 @@ import android.os.ResultReceiver;
import android.os.ServiceSpecificException;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.UserHandle;
import android.provider.Settings;
import android.telephony.CarrierConfigManager;
import android.telephony.Rlog;
@@ -105,9 +115,11 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
/**
@@ -126,6 +138,11 @@ public class SatelliteController extends Handler {
public static final int SATELLITE_MODE_ENABLED_TRUE = 1;
public static final int SATELLITE_MODE_ENABLED_FALSE = 0;
public static final int INVALID_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE = -1;
+ /**
+ * This is used by CTS to override the timeout duration to wait for the response of the request
+ * to enable satellite.
+ */
+ public static final int TIMEOUT_TYPE_WAIT_FOR_SATELLITE_ENABLING_RESPONSE = 1;
/** Key used to read/write OEM-enabled satellite provision status in shared preferences. */
private static final String OEM_ENABLED_SATELLITE_PROVISION_STATUS_KEY =
@@ -169,6 +186,7 @@ public class SatelliteController extends Handler {
private static final int EVENT_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING_DONE = 36;
private static final int EVENT_SERVICE_STATE_CHANGED = 37;
private static final int EVENT_SATELLITE_CAPABILITIES_CHANGED = 38;
+ private static final int EVENT_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMED_OUT = 39;
@NonNull private static SatelliteController sInstance;
@NonNull private final Context mContext;
@@ -334,6 +352,18 @@ public class SatelliteController extends Handler {
* carrierPlmnList. */
@GuardedBy("mSupportedSatelliteServicesLock")
private final SparseArray<List<String>> mMergedPlmnListPerCarrier = new SparseArray<>();
+ private static AtomicLong sNextSatelliteEnableRequestId = new AtomicLong(0);
+ private long mWaitTimeForSatelliteEnablingResponse;
+
+ /** Key used to read/write satellite system notification done in shared preferences. */
+ private static final String SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY =
+ "satellite_system_notification_done_key";
+ // The notification tag used when showing a notification. The combination of notification tag
+ // and notification id should be unique within the phone app.
+ private static final String NOTIFICATION_TAG = "SatelliteController";
+ private static final int NOTIFICATION_ID = 1;
+ private static final String NOTIFICATION_CHANNEL = "satelliteChannel";
+ private static final String NOTIFICATION_CHANNEL_ID = "satellite";
/**
* @return The singleton instance of SatelliteController.
@@ -427,6 +457,7 @@ public class SatelliteController extends Handler {
mDSM.registerForSignalStrengthReportDecision(this, CMD_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING,
null);
loadSatelliteSharedPreferences();
+ mWaitTimeForSatelliteEnablingResponse = getWaitForSatelliteEnablingResponseTimeoutMillis();
}
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@@ -634,12 +665,15 @@ public class SatelliteController extends Handler {
public boolean enableSatellite;
public boolean enableDemoMode;
@NonNull public Consumer<Integer> callback;
+ public long requestId;
RequestSatelliteEnabledArgument(boolean enableSatellite, boolean enableDemoMode,
Consumer<Integer> callback) {
this.enableSatellite = enableSatellite;
this.enableDemoMode = enableDemoMode;
this.callback = callback;
+ this.requestId = sNextSatelliteEnableRequestId.getAndUpdate(
+ n -> ((n + 1) % Long.MAX_VALUE));
}
}
@@ -792,6 +826,17 @@ public class SatelliteController extends Handler {
int error = SatelliteServiceUtils.getSatelliteError(ar, "setSatelliteEnabled");
logd("EVENT_SET_SATELLITE_ENABLED_DONE = " + error);
+ /*
+ * The timer to wait for EVENT_SET_SATELLITE_ENABLED_DONE might have expired and
+ * thus the request resources might have been cleaned up.
+ */
+ if (!shouldProcessEventSetSatelliteEnabledDone(argument)) {
+ logw("The request ID=" + argument.requestId + ", enableSatellite="
+ + argument.enableSatellite + " was already processed");
+ return;
+ }
+ stopWaitForSatelliteEnablingResponseTimer(argument);
+
if (error == SATELLITE_RESULT_SUCCESS) {
if (argument.enableSatellite) {
synchronized (mSatelliteEnabledRequestLock) {
@@ -867,6 +912,11 @@ public class SatelliteController extends Handler {
break;
}
+ case EVENT_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMED_OUT:
+ handleEventWaitForSatelliteEnablingResponseTimedOut(
+ (RequestSatelliteEnabledArgument) msg.obj);
+ break;
+
case CMD_IS_SATELLITE_ENABLED: {
request = (SatelliteControllerHandlerRequest) msg.obj;
onCompleted = obtainMessage(EVENT_IS_SATELLITE_ENABLED_DONE, request);
@@ -2143,18 +2193,56 @@ public class SatelliteController extends Handler {
}
/**
- * This API can be used by only CTS to update the timeout duration in milliseconds whether
- * the device is aligned with the satellite for demo mode
+ * This API can be used by only CTS to override timeout durations used by DatagramController
+ * module.
+ *
+ * @param timeoutMillis The timeout duration in millisecond.
+ * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise.
+ */
+ public boolean setDatagramControllerTimeoutDuration(
+ boolean reset, int timeoutType, long timeoutMillis) {
+ if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+ logd("setDatagramControllerTimeoutDuration: oemEnabledSatelliteFlag is disabled");
+ return false;
+ }
+ logd("setDatagramControllerTimeoutDuration: reset=" + reset + ", timeoutType="
+ + timeoutType + ", timeoutMillis=" + timeoutMillis);
+ return mDatagramController.setDatagramControllerTimeoutDuration(
+ reset, timeoutType, timeoutMillis);
+ }
+
+ /**
+ * This API can be used by only CTS to override timeout durations used by SatelliteController
+ * module.
*
* @param timeoutMillis The timeout duration in millisecond.
* @return {@code true} if the timeout duration is set successfully, {@code false} otherwise.
*/
- public boolean setSatelliteDeviceAlignedTimeoutDuration(long timeoutMillis) {
+ public boolean setSatelliteControllerTimeoutDuration(
+ boolean reset, int timeoutType, long timeoutMillis) {
if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
- logd("setSatelliteDeviceAlignedTimeoutDuration: oemEnabledSatelliteFlag is disabled");
+ logd("setSatelliteControllerTimeoutDuration: oemEnabledSatelliteFlag is disabled");
+ return false;
+ }
+ if (!isMockModemAllowed()) {
+ logd("setSatelliteControllerTimeoutDuration: mock modem is not allowed");
return false;
}
- return mDatagramController.setSatelliteDeviceAlignedTimeoutDuration(timeoutMillis);
+ logd("setSatelliteControllerTimeoutDuration: reset=" + reset + ", timeoutType="
+ + timeoutType + ", timeoutMillis=" + timeoutMillis);
+ if (timeoutType == TIMEOUT_TYPE_WAIT_FOR_SATELLITE_ENABLING_RESPONSE) {
+ if (reset) {
+ mWaitTimeForSatelliteEnablingResponse =
+ getWaitForSatelliteEnablingResponseTimeoutMillis();
+ } else {
+ mWaitTimeForSatelliteEnablingResponse = timeoutMillis;
+ }
+ logd("mWaitTimeForSatelliteEnablingResponse=" + mWaitTimeForSatelliteEnablingResponse);
+ } else {
+ logw("Invalid timeoutType=" + timeoutType);
+ return false;
+ }
+ return true;
}
/**
@@ -2513,19 +2601,28 @@ public class SatelliteController extends Handler {
}
};
}
+ logd("onSatelliteEntitlementStatusUpdated subId=" + subId + " , entitlementEnabled="
+ + entitlementEnabled + ", allowedPlmnList=" + (Objects.equals(null, allowedPlmnList)
+ ? "" : allowedPlmnList + ""));
synchronized (mSupportedSatelliteServicesLock) {
if (mSatelliteEntitlementStatusPerCarrier.get(subId, false) != entitlementEnabled) {
logd("update the carrier satellite enabled to " + entitlementEnabled);
mSatelliteEntitlementStatusPerCarrier.put(subId, entitlementEnabled);
+ try {
+ mSubscriptionManagerService.setSubscriptionProperty(subId,
+ SATELLITE_ENTITLEMENT_STATUS, entitlementEnabled ? "1" : "0");
+ } catch (IllegalArgumentException | SecurityException e) {
+ loge("onSatelliteEntitlementStatusUpdated: setSubscriptionProperty, e=" + e);
+ }
}
mMergedPlmnListPerCarrier.remove(subId);
mEntitlementPlmnListPerCarrier.put(subId, allowedPlmnList);
updatePlmnListPerCarrier(subId);
configureSatellitePlmnForCarrier(subId);
+ mSubscriptionManagerService.setSatelliteEntitlementPlmnList(subId, allowedPlmnList);
- // TODO b/322143408 store entitlement status in telephony db.
if (mSatelliteEntitlementStatusPerCarrier.get(subId, false)) {
removeAttachRestrictionForCarrier(subId,
SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT, callback);
@@ -2726,6 +2823,7 @@ public class SatelliteController extends Handler {
Message onCompleted = obtainMessage(EVENT_SET_SATELLITE_ENABLED_DONE, request);
mSatelliteModemInterface.requestSatelliteEnabled(argument.enableSatellite,
argument.enableDemoMode, onCompleted);
+ startWaitForSatelliteEnablingResponseTimer(argument);
}
private void handleRequestSatelliteAttachRestrictionForCarrierCmd(
@@ -3144,7 +3242,8 @@ public class SatelliteController extends Handler {
PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId,
KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
- KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT);
+ KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT,
+ KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL);
if (config == null || config.isEmpty()) {
config = CarrierConfigManager.getDefaultConfig();
}
@@ -3161,8 +3260,7 @@ public class SatelliteController extends Handler {
}
updateCarrierConfig(subId);
- // TODO b/322143408 read the telephony db to get the entitlementStatus and update the
- // restriction
+ updateEntitlementPlmnListPerCarrier(subId);
updateSupportedSatelliteServicesForActiveSubscriptions();
configureSatellitePlmnForCarrier(subId);
@@ -3172,6 +3270,7 @@ public class SatelliteController extends Handler {
}
setSatelliteAttachEnabledForCarrierOnSimLoaded(subId);
+ updateRestrictReasonForEntitlementPerCarrier(subId);
}
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@@ -3181,6 +3280,31 @@ public class SatelliteController extends Handler {
}
}
+ /** If there is no cached entitlement plmn list, read it from the db and use it if it is not an
+ * empty list. */
+ private void updateEntitlementPlmnListPerCarrier(int subId) {
+ if (!getConfigForSubId(subId).getBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false)) {
+ logd("don't support entitlement");
+ return;
+ }
+
+ synchronized (mSupportedSatelliteServicesLock) {
+ if (mEntitlementPlmnListPerCarrier.indexOfKey(subId) < 0) {
+ logd("updateEntitlementPlmnListPerCarrier: no correspondent cache, load from "
+ + "persist storage");
+ List<String> entitlementPlmnList =
+ mSubscriptionManagerService.getSatelliteEntitlementPlmnList(subId);
+ if (entitlementPlmnList.isEmpty()) {
+ loge("updateEntitlementPlmnListPerCarrier: no data for subId(" + subId + ")");
+ return;
+ }
+ logd("updateEntitlementPlmnListPerCarrier: entitlementPlmnList="
+ + entitlementPlmnList);
+ mEntitlementPlmnListPerCarrier.put(subId, entitlementPlmnList);
+ }
+ }
+ }
+
/**
* When a SIM is loaded, we need to check if users has enabled satellite attach for the carrier
* associated with the SIM, and evaluate if satellite should be enabled for the carrier.
@@ -3285,6 +3409,54 @@ public class SatelliteController extends Handler {
}
}
+ private void updateRestrictReasonForEntitlementPerCarrier(int subId) {
+ if (!getConfigForSubId(subId).getBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false)) {
+ logd("don't support entitlement");
+ return;
+ }
+
+ IIntegerConsumer callback = new IIntegerConsumer.Stub() {
+ @Override
+ public void accept(int result) {
+ logd("updateRestrictReasonForEntitlementPerCarrier:" + result);
+ }
+ };
+ synchronized (mSupportedSatelliteServicesLock) {
+ if (mSatelliteEntitlementStatusPerCarrier.indexOfKey(subId) < 0) {
+ logd("updateRestrictReasonForEntitlementPerCarrier: no correspondent cache, "
+ + "load from persist storage");
+ String entitlementStatus = null;
+ try {
+ entitlementStatus =
+ mSubscriptionManagerService.getSubscriptionProperty(subId,
+ SATELLITE_ENTITLEMENT_STATUS, mContext.getOpPackageName(),
+ mContext.getAttributionTag());
+ } catch (IllegalArgumentException | SecurityException e) {
+ loge("updateRestrictReasonForEntitlementPerCarrier, e=" + e);
+ }
+
+ if (entitlementStatus == null) {
+ loge("updateRestrictReasonForEntitlementPerCarrier: invalid subId, subId="
+ + subId + " set to default value");
+ entitlementStatus = "0";
+ }
+
+ if (entitlementStatus.isEmpty()) {
+ loge("updateRestrictReasonForEntitlementPerCarrier: no data for subId(" + subId
+ + "). set to default value");
+ entitlementStatus = "0";
+ }
+ boolean result = entitlementStatus.equals("1");
+ mSatelliteEntitlementStatusPerCarrier.put(subId, result);
+ }
+
+ if (!mSatelliteEntitlementStatusPerCarrier.get(subId, false)) {
+ addAttachRestrictionForCarrier(subId,
+ SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT, callback);
+ }
+ }
+ }
+
/**
* Save user setting for enabling satellite attach for the carrier associated with the
* {@code subId} to persistent storage.
@@ -3462,6 +3634,7 @@ public class SatelliteController extends Handler {
private void handleEventServiceStateChanged() {
handleServiceStateForSatelliteConnectionViaCarrier();
+ determineSystemNotification();
}
private void handleServiceStateForSatelliteConnectionViaCarrier() {
@@ -3573,6 +3746,109 @@ public class SatelliteController extends Handler {
((ResultReceiver) request.argument).send(error, bundle);
}
+ private long getWaitForSatelliteEnablingResponseTimeoutMillis() {
+ return mContext.getResources().getInteger(
+ R.integer.config_wait_for_satellite_enabling_response_timeout_millis);
+ }
+
+ private void startWaitForSatelliteEnablingResponseTimer(
+ @NonNull RequestSatelliteEnabledArgument argument) {
+ synchronized (mSatelliteEnabledRequestLock) {
+ if (hasMessages(EVENT_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMED_OUT, argument)) {
+ logd("WaitForSatelliteEnablingResponseTimer of request ID "
+ + argument.requestId + " was already started");
+ return;
+ }
+ logd("Start timer to wait for response of the satellite enabling request ID="
+ + argument.requestId + ", enableSatellite=" + argument.enableSatellite
+ + ", mWaitTimeForSatelliteEnablingResponse="
+ + mWaitTimeForSatelliteEnablingResponse);
+ sendMessageDelayed(obtainMessage(EVENT_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMED_OUT,
+ argument), mWaitTimeForSatelliteEnablingResponse);
+ }
+ }
+
+ private void stopWaitForSatelliteEnablingResponseTimer(
+ @NonNull RequestSatelliteEnabledArgument argument) {
+ synchronized (mSatelliteEnabledRequestLock) {
+ logd("Stop timer to wait for response of the satellite enabling request ID="
+ + argument.requestId + ", enableSatellite=" + argument.enableSatellite);
+ removeMessages(EVENT_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMED_OUT, argument);
+ }
+ }
+
+ private boolean shouldProcessEventSetSatelliteEnabledDone(
+ @NonNull RequestSatelliteEnabledArgument argument) {
+ synchronized (mSatelliteEnabledRequestLock) {
+ if (hasMessages(EVENT_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMED_OUT, argument)) {
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private void handleEventWaitForSatelliteEnablingResponseTimedOut(
+ @NonNull RequestSatelliteEnabledArgument argument) {
+ logw("Timed out to wait for response of the satellite enabling request ID="
+ + argument.requestId + ", enableSatellite=" + argument.enableSatellite);
+
+ synchronized (mSatelliteEnabledRequestLock) {
+ if (mSatelliteEnabledRequest != null) {
+ if (mSatelliteEnabledRequest.enableSatellite && !argument.enableSatellite
+ && mWaitingForRadioDisabled) {
+ // Previous mSatelliteEnabledRequest is successful but waiting for
+ // all radios to be turned off.
+ mSatelliteEnabledRequest.callback.accept(SATELLITE_RESULT_SUCCESS);
+ resetSatelliteEnabledRequest();
+ } else if (mSatelliteEnabledRequest.requestId == argument.requestId) {
+ resetSatelliteEnabledRequest();
+ }
+ }
+ }
+ argument.callback.accept(SATELLITE_RESULT_MODEM_TIMEOUT);
+
+ synchronized (mIsSatelliteEnabledLock) {
+ if (argument.enableSatellite) {
+ if (!mWaitingForDisableSatelliteModemResponse && !mWaitingForSatelliteModemOff) {
+ resetSatelliteEnabledRequest();
+ IIntegerConsumer callback = new IIntegerConsumer.Stub() {
+ @Override
+ public void accept(int result) {
+ logd("handleEventWaitForSatelliteEnablingResponseTimedOut: "
+ + "disable satellite result=" + result);
+ }
+ };
+ Consumer<Integer> result =
+ FunctionalUtils.ignoreRemoteException(callback::accept);
+ RequestSatelliteEnabledArgument request = new RequestSatelliteEnabledArgument(
+ false, false, result);
+ synchronized (mSatelliteEnabledRequestLock) {
+ mSatelliteEnabledRequest = request;
+ }
+ sendRequestAsync(CMD_SET_SATELLITE_ENABLED, request, null);
+ }
+ mControllerMetricsStats.reportServiceEnablementFailCount();
+ SessionMetricsStats.getInstance()
+ .setInitializationResult(SATELLITE_RESULT_MODEM_TIMEOUT)
+ .setRadioTechnology(getSupportedNtnRadioTechnology())
+ .reportSessionMetrics();
+ } else {
+ /*
+ * Unregister Importance Listener for Pointing UI when Satellite is disabled
+ */
+ synchronized (mNeedsSatellitePointingLock) {
+ if (mNeedsSatellitePointing) {
+ mPointingAppController.removeListenerForPointingUI();
+ }
+ }
+ moveSatelliteToOffStateAndCleanUpResources(SATELLITE_RESULT_MODEM_TIMEOUT, null);
+ mControllerMetricsStats.onSatelliteDisabled();
+ mWaitingForDisableSatelliteModemResponse = false;
+ mWaitingForSatelliteModemOff = false;
+ }
+ }
+ }
+
/**
* This API can be used by only CTS to override the cached value for the device overlay config
* value : config_send_satellite_datagram_to_modem_in_demo_mode, which determines whether
@@ -3598,10 +3874,86 @@ public class SatelliteController extends Handler {
return true;
}
+ private void determineSystemNotification() {
+ if (isUsingNonTerrestrialNetworkViaCarrier()) {
+ if (mSharedPreferences == null) {
+ try {
+ mSharedPreferences = mContext.getSharedPreferences(SATELLITE_SHARED_PREF,
+ Context.MODE_PRIVATE);
+ } catch (Exception e) {
+ loge("Cannot get default shared preferences: " + e);
+ }
+ }
+ if (mSharedPreferences == null) {
+ loge("handleEventServiceStateChanged: Cannot get default shared preferences");
+ return;
+ }
+ if (!mSharedPreferences.getBoolean(SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY, false)) {
+ showSatelliteSystemNotification();
+ mSharedPreferences.edit().putBoolean(SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY,
+ true).apply();
+ }
+ }
+ }
+
+ private void showSatelliteSystemNotification() {
+ logd("showSatelliteSystemNotification");
+ final NotificationChannel notificationChannel = new NotificationChannel(
+ NOTIFICATION_CHANNEL_ID,
+ NOTIFICATION_CHANNEL,
+ NotificationManager.IMPORTANCE_DEFAULT
+ );
+ notificationChannel.setSound(null, null);
+ NotificationManager notificationManager = mContext.getSystemService(
+ NotificationManager.class);
+ notificationManager.createNotificationChannel(notificationChannel);
+
+ Notification.Builder notificationBuilder = new Notification.Builder(mContext)
+ .setContentTitle(mContext.getResources().getString(
+ R.string.satellite_notification_title))
+ .setContentText(mContext.getResources().getString(
+ R.string.satellite_notification_summary))
+ .setSmallIcon(R.drawable.ic_satellite_alt_24px)
+ .setChannelId(NOTIFICATION_CHANNEL_ID)
+ .setAutoCancel(true)
+ .setColor(mContext.getColor(
+ com.android.internal.R.color.system_notification_accent_color))
+ .setVisibility(Notification.VISIBILITY_PUBLIC);
+
+ // Add action to invoke `What to expect` dialog of Messaging application.
+ Intent intentOpenMessage = new Intent(Intent.ACTION_VIEW);
+ intentOpenMessage.setData(Uri.parse("sms:"));
+ // TODO : b/322733285 add putExtra to invoke "What to expect" dialog.
+ PendingIntent pendingIntentOpenMessage = PendingIntent.getActivity(mContext, 0,
+ intentOpenMessage, PendingIntent.FLAG_IMMUTABLE);
+
+ Notification.Action actionOpenMessage = new Notification.Action.Builder(0,
+ mContext.getResources().getString(R.string.satellite_notification_open_message),
+ pendingIntentOpenMessage).build();
+ notificationBuilder.addAction(actionOpenMessage);
+
+ // Add action to invoke Satellite setting activity in Settings.
+ Intent intentSatelliteSetting = new Intent(ACTION_SATELLITE_SETTING);
+ PendingIntent pendingIntentSatelliteSetting = PendingIntent.getActivity(mContext, 0,
+ intentSatelliteSetting, PendingIntent.FLAG_IMMUTABLE);
+
+ Notification.Action actionOpenSatelliteSetting = new Notification.Action.Builder(null,
+ mContext.getResources().getString(R.string.satellite_notification_how_it_works),
+ pendingIntentSatelliteSetting).build();
+ notificationBuilder.addAction(actionOpenSatelliteSetting);
+
+ notificationManager.notifyAsUser(NOTIFICATION_TAG, NOTIFICATION_ID,
+ notificationBuilder.build(), UserHandle.ALL);
+ }
+
private static void logd(@NonNull String log) {
Rlog.d(TAG, log);
}
+ private static void logw(@NonNull String log) {
+ Rlog.w(TAG, log);
+ }
+
private static void loge(@NonNull String log) {
Rlog.e(TAG, log);
}
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java b/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java
index 900e1248c0..2f86eea805 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java
@@ -1317,6 +1317,35 @@ public class SatelliteModemInterface {
}
}
+ /**
+ * The satellite service should abort all datagram-sending requests.
+ *
+ * @param message The Message to send to result of the operation to.
+ */
+ public void abortSendingSatelliteDatagrams(@NonNull Message message) {
+ if (mSatelliteService != null) {
+ try {
+ mSatelliteService.abortSendingSatelliteDatagrams(new IIntegerConsumer.Stub() {
+ @Override
+ public void accept(int result) {
+ int error = SatelliteServiceUtils.fromSatelliteError(result);
+ logd("abortSendingSatelliteDatagrams: " + error);
+ Binder.withCleanCallingIdentity(() ->
+ sendMessageWithResult(message, null, error));
+ }
+ });
+ } catch (RemoteException e) {
+ loge("abortSendingSatelliteDatagrams: RemoteException " + e);
+ sendMessageWithResult(message, null,
+ SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
+ }
+ } else {
+ loge("abortSendingSatelliteDatagrams: Satellite service is unavailable.");
+ sendMessageWithResult(message, null,
+ SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
+ }
+ }
+
public boolean isSatelliteServiceSupported() {
return mIsSatelliteServiceSupported;
}
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java b/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
index 541a0299fd..5c79c28a0f 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
@@ -358,6 +358,7 @@ public class SatelliteSessionController extends StateMachine {
}
return true;
}
+
/**
* Adjusts listening timeout duration when demo mode is on
*
@@ -711,6 +712,9 @@ public class SatelliteSessionController extends StateMachine {
&& datagramTransferState.receiveState
== SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE) {
startNbIotInactivityTimer();
+ } else if (isSending(datagramTransferState.sendState)
+ || isReceiving(datagramTransferState.receiveState)) {
+ restartNbIotInactivityTimer();
}
}
}
@@ -761,9 +765,8 @@ public class SatelliteSessionController extends StateMachine {
private void handleEventDatagramTransferStateChanged(
@NonNull DatagramTransferState datagramTransferState) {
- if (datagramTransferState.sendState == SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING
- || datagramTransferState.receiveState
- == SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING) {
+ if (isSending(datagramTransferState.sendState)
+ || isReceiving(datagramTransferState.receiveState)) {
transitionTo(mTransferringState);
}
}
@@ -974,6 +977,11 @@ public class SatelliteSessionController extends StateMachine {
R.integer.config_satellite_nb_iot_inactivity_timeout_millis);
}
+ private void restartNbIotInactivityTimer() {
+ stopNbIotInactivityTimer();
+ startNbIotInactivityTimer();
+ }
+
private void startNbIotInactivityTimer() {
if (isNbIotInactivityTimerStarted()) {
logd("NB IOT inactivity timer is already started");
diff --git a/src/java/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifier.java b/src/java/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifier.java
index e7f66a32f2..4540b8a93e 100644
--- a/src/java/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifier.java
+++ b/src/java/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifier.java
@@ -16,6 +16,7 @@
package com.android.internal.telephony.security;
+import android.content.Context;
import android.telephony.CellularIdentifierDisclosure;
import com.android.internal.annotations.GuardedBy;
@@ -49,6 +50,7 @@ public class CellularIdentifierDisclosureNotifier {
private static CellularIdentifierDisclosureNotifier sInstance = null;
private final long mWindowCloseDuration;
private final TimeUnit mWindowCloseUnit;
+ private final CellularNetworkSecuritySafetySource mSafetySource;
private final Object mEnabledLock = new Object();
@GuardedBy("mEnabledLock")
@@ -61,11 +63,12 @@ public class CellularIdentifierDisclosureNotifier {
// outside of that thread would require additional synchronization.
private Map<Integer, DisclosureWindow> mWindows;
- public CellularIdentifierDisclosureNotifier() {
+ public CellularIdentifierDisclosureNotifier(CellularNetworkSecuritySafetySource safetySource) {
this(
Executors.newSingleThreadScheduledExecutor(),
DEFAULT_WINDOW_CLOSE_DURATION_IN_MINUTES,
- TimeUnit.MINUTES);
+ TimeUnit.MINUTES,
+ safetySource);
}
/**
@@ -79,18 +82,20 @@ public class CellularIdentifierDisclosureNotifier {
public CellularIdentifierDisclosureNotifier(
ScheduledExecutorService notificationQueue,
long windowCloseDuration,
- TimeUnit windowCloseUnit) {
+ TimeUnit windowCloseUnit,
+ CellularNetworkSecuritySafetySource safetySource) {
mSerializedWorkQueue = notificationQueue;
mWindowCloseDuration = windowCloseDuration;
mWindowCloseUnit = windowCloseUnit;
mWindows = new HashMap<>();
+ mSafetySource = safetySource;
}
/**
* Add a CellularIdentifierDisclosure to be tracked by this instance. If appropriate, this will
* trigger a user notification.
*/
- public void addDisclosure(int subId, CellularIdentifierDisclosure disclosure) {
+ public void addDisclosure(Context context, int subId, CellularIdentifierDisclosure disclosure) {
Rlog.d(TAG, "Identifier disclosure reported: " + disclosure);
synchronized (mEnabledLock) {
@@ -111,7 +116,7 @@ public class CellularIdentifierDisclosureNotifier {
// because we know that any actions taken on disabled will be scheduled after this
// incrementAndNotify call.
try {
- mSerializedWorkQueue.execute(incrementAndNotify(subId));
+ mSerializedWorkQueue.execute(incrementAndNotify(context, subId));
} catch (RejectedExecutionException e) {
Rlog.e(TAG, "Failed to schedule incrementAndNotify: " + e.getMessage());
}
@@ -122,12 +127,12 @@ public class CellularIdentifierDisclosureNotifier {
* Re-enable if previously disabled. This means that {@code addDisclsoure} will start tracking
* disclosures again and potentially emitting notifications.
*/
- public void enable() {
+ public void enable(Context context) {
synchronized (mEnabledLock) {
Rlog.d(TAG, "enabled");
mEnabled = true;
try {
- mSerializedWorkQueue.execute(onEnableNotifier());
+ mSerializedWorkQueue.execute(onEnableNotifier(context));
} catch (RejectedExecutionException e) {
Rlog.e(TAG, "Failed to schedule onEnableNotifier: " + e.getMessage());
}
@@ -139,12 +144,12 @@ public class CellularIdentifierDisclosureNotifier {
* This can be used to in response to a user disabling the feature to emit notifications.
* If {@code addDisclosure} is called while in a disabled state, disclosures will be dropped.
*/
- public void disable() {
+ public void disable(Context context) {
Rlog.d(TAG, "disabled");
synchronized (mEnabledLock) {
mEnabled = false;
try {
- mSerializedWorkQueue.execute(onDisableNotifier());
+ mSerializedWorkQueue.execute(onDisableNotifier(context));
} catch (RejectedExecutionException e) {
Rlog.e(TAG, "Failed to schedule onDisableNotifier: " + e.getMessage());
}
@@ -158,15 +163,16 @@ public class CellularIdentifierDisclosureNotifier {
}
/** Get a singleton CellularIdentifierDisclosureNotifier. */
- public static synchronized CellularIdentifierDisclosureNotifier getInstance() {
+ public static synchronized CellularIdentifierDisclosureNotifier getInstance(
+ CellularNetworkSecuritySafetySource safetySource) {
if (sInstance == null) {
- sInstance = new CellularIdentifierDisclosureNotifier();
+ sInstance = new CellularIdentifierDisclosureNotifier(safetySource);
}
return sInstance;
}
- private Runnable incrementAndNotify(int subId) {
+ private Runnable incrementAndNotify(Context context, int subId) {
return () -> {
DisclosureWindow window = mWindows.get(subId);
if (window == null) {
@@ -174,7 +180,7 @@ public class CellularIdentifierDisclosureNotifier {
mWindows.put(subId, window);
}
- window.increment(this);
+ window.increment(context, this);
int disclosureCount = window.getDisclosureCount();
@@ -185,31 +191,29 @@ public class CellularIdentifierDisclosureNotifier {
+ ". New disclosure count "
+ disclosureCount);
- // TODO (b/308985417) emit safety center issue
- // mSafetySource.setIdentifierDisclosure(
- // subId,
- // disclosureCount,
- // window.getFirstOpen(),
- // window.getCurrentEnd());
+ mSafetySource.setIdentifierDisclosure(
+ context,
+ subId,
+ disclosureCount,
+ window.getFirstOpen(),
+ window.getCurrentEnd());
};
}
- private Runnable onDisableNotifier() {
+ private Runnable onDisableNotifier(Context context) {
return () -> {
Rlog.d(TAG, "On disable notifier");
for (DisclosureWindow window : mWindows.values()) {
window.close();
}
- // TODO (b/308985417) disable safety center issues
- // mSafetySource.setIdentifierDisclosureIssueEnabled(false);
+ mSafetySource.setIdentifierDisclosureIssueEnabled(context, false);
};
}
- private Runnable onEnableNotifier() {
+ private Runnable onEnableNotifier(Context context) {
return () -> {
Rlog.i(TAG, "On enable notifier");
- // TODO (b/308985417) enable safety center issues
- // mSafetySource.setIdentifierDisclosureIssueEnabled(true);
+ mSafetySource.setIdentifierDisclosureIssueEnabled(context, true);
};
}
@@ -262,7 +266,7 @@ public class CellularIdentifierDisclosureNotifier {
* A helper class that maintains all state associated with the disclosure window for a single
* subId. No methods are thread safe. Callers must implement all synchronization.
*/
- private static class DisclosureWindow {
+ private class DisclosureWindow {
private int mDisclosureCount;
private Instant mWindowFirstOpen;
private Instant mLastEvent;
@@ -278,7 +282,7 @@ public class CellularIdentifierDisclosureNotifier {
mWhenWindowCloses = null;
}
- void increment(CellularIdentifierDisclosureNotifier notifier) {
+ void increment(Context context, CellularIdentifierDisclosureNotifier notifier) {
mDisclosureCount++;
@@ -295,7 +299,7 @@ public class CellularIdentifierDisclosureNotifier {
try {
mWhenWindowCloses =
notifier.mSerializedWorkQueue.schedule(
- closeWindowRunnable(),
+ closeWindowRunnable(context),
notifier.mWindowCloseDuration,
notifier.mWindowCloseUnit);
} catch (RejectedExecutionException e) {
@@ -331,7 +335,7 @@ public class CellularIdentifierDisclosureNotifier {
mWhenWindowCloses = null;
}
- private Runnable closeWindowRunnable() {
+ private Runnable closeWindowRunnable(Context context) {
return () -> {
Rlog.i(
TAG,
@@ -340,9 +344,7 @@ public class CellularIdentifierDisclosureNotifier {
+ ". Disclosure count was "
+ getDisclosureCount());
close();
-
- // TODO (b/308985417) clear safety center issue
- // mSafetySource.setIdentifierDisclosure(mSubId, 0, null, null);
+ mSafetySource.clearIdentifierDisclosure(context, mSubId);
};
}
diff --git a/src/java/com/android/internal/telephony/security/CellularNetworkSecuritySafetySource.java b/src/java/com/android/internal/telephony/security/CellularNetworkSecuritySafetySource.java
new file mode 100644
index 0000000000..34f26e3c6d
--- /dev/null
+++ b/src/java/com/android/internal/telephony/security/CellularNetworkSecuritySafetySource.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.security;
+
+import static android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED;
+import static android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED;
+import static android.safetycenter.SafetySourceData.SEVERITY_LEVEL_INFORMATION;
+import static android.safetycenter.SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION;
+
+import android.annotation.IntDef;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.safetycenter.SafetyCenterManager;
+import android.safetycenter.SafetyEvent;
+import android.safetycenter.SafetySourceData;
+import android.safetycenter.SafetySourceIssue;
+import android.safetycenter.SafetySourceStatus;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.time.Instant;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+/**
+ * Holds the state needed to report the Safety Center status and issues related to cellular
+ * network security.
+ */
+public class CellularNetworkSecuritySafetySource {
+ private static final String SAFETY_SOURCE_ID = "AndroidCellularNetworkSecurity";
+
+ private static final String NULL_CIPHER_ISSUE_NON_ENCRYPTED_ID = "null_cipher_non_encrypted";
+ private static final String NULL_CIPHER_ISSUE_ENCRYPTED_ID = "null_cipher_encrypted";
+
+ private static final String NULL_CIPHER_ACTION_SETTINGS_ID = "cellular_security_settings";
+ private static final String NULL_CIPHER_ACTION_LEARN_MORE_ID = "learn_more";
+
+ private static final String IDENTIFIER_DISCLOSURE_ISSUE_ID = "identifier_disclosure";
+
+ private static final Intent CELLULAR_NETWORK_SECURITY_SETTINGS_INTENT =
+ new Intent("android.settings.CELLULAR_NETWORK_SECURITY");
+ // TODO(b/321999913): direct to a help page URL e.g.
+ // new Intent(Intent.ACTION_VIEW, Uri.parse("https://..."));
+ private static final Intent LEARN_MORE_INTENT = new Intent();
+
+ static final int NULL_CIPHER_STATE_ENCRYPTED = 0;
+ static final int NULL_CIPHER_STATE_NOTIFY_ENCRYPTED = 1;
+ static final int NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED = 2;
+
+ @IntDef(
+ prefix = {"NULL_CIPHER_STATE_"},
+ value = {
+ NULL_CIPHER_STATE_ENCRYPTED,
+ NULL_CIPHER_STATE_NOTIFY_ENCRYPTED,
+ NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface NullCipherState {}
+
+ private static CellularNetworkSecuritySafetySource sInstance;
+
+ private final SafetyCenterManagerWrapper mSafetyCenterManagerWrapper;
+ private final SubscriptionManagerService mSubscriptionManagerService;
+
+ private boolean mNullCipherStateIssuesEnabled;
+ private HashMap<Integer, Integer> mNullCipherStates = new HashMap<>();
+
+ private boolean mIdentifierDisclosureIssuesEnabled;
+ private HashMap<Integer, IdentifierDisclosure> mIdentifierDisclosures = new HashMap<>();
+
+ /**
+ * Gets a singleton CellularNetworkSecuritySafetySource.
+ */
+ public static synchronized CellularNetworkSecuritySafetySource getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new CellularNetworkSecuritySafetySource(
+ new SafetyCenterManagerWrapper(context));
+ }
+ return sInstance;
+ }
+
+ @VisibleForTesting
+ public CellularNetworkSecuritySafetySource(
+ SafetyCenterManagerWrapper safetyCenterManagerWrapper) {
+ mSafetyCenterManagerWrapper = safetyCenterManagerWrapper;
+ mSubscriptionManagerService = SubscriptionManagerService.getInstance();
+ }
+
+ /** Enables or disables the null cipher issue and clears any current issues. */
+ public synchronized void setNullCipherIssueEnabled(Context context, boolean enabled) {
+ mNullCipherStateIssuesEnabled = enabled;
+ mNullCipherStates.clear();
+ updateSafetyCenter(context);
+ }
+
+ /** Sets the null cipher issue state for the identified subscription. */
+ public synchronized void setNullCipherState(
+ Context context, int subId, @NullCipherState int nullCipherState) {
+ mNullCipherStates.put(subId, nullCipherState);
+ updateSafetyCenter(context);
+ }
+
+ /**
+ * Enables or disables the identifier disclosure issue and clears any current issues if the
+ * enable state is changed.
+ */
+ public synchronized void setIdentifierDisclosureIssueEnabled(Context context, boolean enabled) {
+ // This check ensures that if we're enabled and we are asked to enable ourselves again (can
+ // happen if the modem restarts), we don't clear our state.
+ if (enabled != mIdentifierDisclosureIssuesEnabled) {
+ mIdentifierDisclosureIssuesEnabled = enabled;
+ mIdentifierDisclosures.clear();
+ updateSafetyCenter(context);
+ }
+ }
+
+ /** Sets the identifier disclosure issue state for the identifier subscription. */
+ public synchronized void setIdentifierDisclosure(
+ Context context, int subId, int count, Instant start, Instant end) {
+ IdentifierDisclosure disclosure = new IdentifierDisclosure(count, start, end);
+ mIdentifierDisclosures.put(subId, disclosure);
+ updateSafetyCenter(context);
+ }
+
+ /** Clears the identifier disclosure issue state for the identified subscription. */
+ public synchronized void clearIdentifierDisclosure(Context context, int subId) {
+ mIdentifierDisclosures.remove(subId);
+ updateSafetyCenter(context);
+ }
+
+ /** Refreshed the safety source in response to the identified broadcast. */
+ public synchronized void refresh(Context context, String refreshBroadcastId) {
+ mSafetyCenterManagerWrapper.setRefreshedSafetySourceData(
+ refreshBroadcastId, getSafetySourceData(context));
+ }
+
+ private void updateSafetyCenter(Context context) {
+ mSafetyCenterManagerWrapper.setSafetySourceData(getSafetySourceData(context));
+ }
+
+ private boolean isSafetySourceHidden() {
+ return !mNullCipherStateIssuesEnabled && !mIdentifierDisclosureIssuesEnabled;
+ }
+
+ private SafetySourceData getSafetySourceData(Context context) {
+ if (isSafetySourceHidden()) {
+ // The cellular network security safety source is configured with
+ // initialDisplayState="hidden"
+ return null;
+ }
+
+ Stream<Optional<SafetySourceIssue>> nullCipherIssues =
+ mNullCipherStates.entrySet().stream()
+ .map(e -> getNullCipherIssue(context, e.getKey(), e.getValue()));
+ Stream<Optional<SafetySourceIssue>> identifierDisclosureIssues =
+ mIdentifierDisclosures.entrySet().stream()
+ .map(e -> getIdentifierDisclosureIssue(context, e.getKey(), e.getValue()));
+ SafetySourceIssue[] issues = Stream.concat(nullCipherIssues, identifierDisclosureIssues)
+ .flatMap(Optional::stream)
+ .toArray(SafetySourceIssue[]::new);
+
+ SafetySourceData.Builder builder = new SafetySourceData.Builder();
+ int maxSeverity = SEVERITY_LEVEL_INFORMATION;
+ for (SafetySourceIssue issue : issues) {
+ builder.addIssue(issue);
+ maxSeverity = Math.max(maxSeverity, issue.getSeverityLevel());
+ }
+
+ builder.setStatus(
+ new SafetySourceStatus.Builder(
+ context.getString(R.string.scCellularNetworkSecurityTitle),
+ context.getString(R.string.scCellularNetworkSecuritySummary),
+ maxSeverity)
+ .setPendingIntent(mSafetyCenterManagerWrapper.getActivityPendingIntent(
+ context, CELLULAR_NETWORK_SECURITY_SETTINGS_INTENT))
+ .build());
+ return builder.build();
+ }
+
+ /** Builds the null cipher issue if it's enabled and there are null ciphers to report. */
+ private Optional<SafetySourceIssue> getNullCipherIssue(
+ Context context, int subId, @NullCipherState int state) {
+ if (!mNullCipherStateIssuesEnabled) {
+ return Optional.empty();
+ }
+
+ SubscriptionInfoInternal subInfo =
+ mSubscriptionManagerService.getSubscriptionInfoInternal(subId);
+ final SafetySourceIssue.Builder builder;
+ switch (state) {
+ case NULL_CIPHER_STATE_ENCRYPTED:
+ return Optional.empty();
+ case NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED:
+ builder = new SafetySourceIssue.Builder(
+ NULL_CIPHER_ISSUE_NON_ENCRYPTED_ID + "_" + subId,
+ context.getString(
+ R.string.scNullCipherIssueNonEncryptedTitle, subInfo.getDisplayName()),
+ context.getString(R.string.scNullCipherIssueNonEncryptedSummary),
+ SEVERITY_LEVEL_RECOMMENDATION,
+ NULL_CIPHER_ISSUE_NON_ENCRYPTED_ID);
+ break;
+ case NULL_CIPHER_STATE_NOTIFY_ENCRYPTED:
+ builder = new SafetySourceIssue.Builder(
+ NULL_CIPHER_ISSUE_NON_ENCRYPTED_ID + "_" + subId,
+ context.getString(
+ R.string.scNullCipherIssueEncryptedTitle, subInfo.getDisplayName()),
+ context.getString(R.string.scNullCipherIssueEncryptedSummary),
+ SEVERITY_LEVEL_INFORMATION,
+ NULL_CIPHER_ISSUE_ENCRYPTED_ID);
+ break;
+ default:
+ throw new AssertionError();
+ }
+
+ return Optional.of(
+ builder
+ .setNotificationBehavior(SafetySourceIssue.NOTIFICATION_BEHAVIOR_IMMEDIATELY)
+ .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DEVICE)
+ .addAction(
+ new SafetySourceIssue.Action.Builder(
+ NULL_CIPHER_ACTION_SETTINGS_ID,
+ context.getString(R.string.scNullCipherIssueActionSettings),
+ mSafetyCenterManagerWrapper.getActivityPendingIntent(
+ context, CELLULAR_NETWORK_SECURITY_SETTINGS_INTENT))
+ .build())
+ .addAction(
+ new SafetySourceIssue.Action.Builder(
+ NULL_CIPHER_ACTION_LEARN_MORE_ID,
+ context.getString(R.string.scNullCipherIssueActionLearnMore),
+ mSafetyCenterManagerWrapper.getActivityPendingIntent(
+ context, LEARN_MORE_INTENT))
+ .build())
+ .build());
+ }
+
+ /** Builds the identity disclosure issue if it's enabled and there are disclosures to report. */
+ private Optional<SafetySourceIssue> getIdentifierDisclosureIssue(
+ Context context, int subId, IdentifierDisclosure disclosure) {
+ if (!mIdentifierDisclosureIssuesEnabled || disclosure.getDisclosureCount() == 0) {
+ return Optional.empty();
+ }
+
+ SubscriptionInfoInternal subInfo =
+ mSubscriptionManagerService.getSubscriptionInfoInternal(subId);
+ return Optional.of(
+ new SafetySourceIssue.Builder(
+ IDENTIFIER_DISCLOSURE_ISSUE_ID + "_" + subId,
+ context.getString(R.string.scIdentifierDisclosureIssueTitle),
+ context.getString(
+ R.string.scIdentifierDisclosureIssueSummary,
+ disclosure.getDisclosureCount(),
+ Date.from(disclosure.getWindowStart()),
+ Date.from(disclosure.getWindowEnd()),
+ subInfo.getDisplayName()),
+ SEVERITY_LEVEL_RECOMMENDATION,
+ IDENTIFIER_DISCLOSURE_ISSUE_ID)
+ .setNotificationBehavior(SafetySourceIssue.NOTIFICATION_BEHAVIOR_IMMEDIATELY)
+ .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DEVICE)
+ .addAction(
+ new SafetySourceIssue.Action.Builder(
+ NULL_CIPHER_ACTION_SETTINGS_ID,
+ context.getString(R.string.scNullCipherIssueActionSettings),
+ mSafetyCenterManagerWrapper.getActivityPendingIntent(
+ context, CELLULAR_NETWORK_SECURITY_SETTINGS_INTENT))
+ .build())
+ .addAction(
+ new SafetySourceIssue.Action.Builder(
+ NULL_CIPHER_ACTION_LEARN_MORE_ID,
+ context.getString(R.string.scNullCipherIssueActionLearnMore),
+ mSafetyCenterManagerWrapper.getActivityPendingIntent(
+ context, LEARN_MORE_INTENT))
+ .build())
+ .build());
+ }
+
+ /** A wrapper around {@link SafetyCenterManager} that can be instrumented in tests. */
+ @VisibleForTesting
+ public static class SafetyCenterManagerWrapper {
+ private final SafetyCenterManager mSafetyCenterManager;
+
+ public SafetyCenterManagerWrapper(Context context) {
+ mSafetyCenterManager = context.getSystemService(SafetyCenterManager.class);
+ }
+
+ /** Retrieve a {@link PendingIntent} that will start a new activity. */
+ public PendingIntent getActivityPendingIntent(Context context, Intent intent) {
+ return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
+ }
+
+ /** Set the {@link SafetySourceData} for this safety source. */
+ public void setSafetySourceData(SafetySourceData safetySourceData) {
+ mSafetyCenterManager.setSafetySourceData(
+ SAFETY_SOURCE_ID,
+ safetySourceData,
+ new SafetyEvent.Builder(SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build());
+ }
+
+ /** Sets the {@link SafetySourceData} in response to a refresh request. */
+ public void setRefreshedSafetySourceData(
+ String refreshBroadcastId, SafetySourceData safetySourceData) {
+ mSafetyCenterManager.setSafetySourceData(
+ SAFETY_SOURCE_ID,
+ safetySourceData,
+ new SafetyEvent.Builder(SAFETY_EVENT_TYPE_REFRESH_REQUESTED)
+ .setRefreshBroadcastId(refreshBroadcastId)
+ .build());
+ }
+ }
+
+ private static class IdentifierDisclosure {
+ private final int mDisclosureCount;
+ private final Instant mWindowStart;
+ private final Instant mWindowEnd;
+
+ private IdentifierDisclosure(int count, Instant start, Instant end) {
+ mDisclosureCount = count;
+ mWindowStart = start;
+ mWindowEnd = end;
+ }
+
+ private int getDisclosureCount() {
+ return mDisclosureCount;
+ }
+
+ private Instant getWindowStart() {
+ return mWindowStart;
+ }
+
+ private Instant getWindowEnd() {
+ return mWindowEnd;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof IdentifierDisclosure)) {
+ return false;
+ }
+ IdentifierDisclosure other = (IdentifierDisclosure) o;
+ return mDisclosureCount == other.mDisclosureCount
+ && Objects.equals(mWindowStart, other.mWindowStart)
+ && Objects.equals(mWindowEnd, other.mWindowEnd);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mDisclosureCount, mWindowStart, mWindowEnd);
+ }
+ }
+}
diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java b/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java
index bbe88a8c17..3d07d47388 100644
--- a/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java
+++ b/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java
@@ -284,7 +284,16 @@ public class SubscriptionDatabaseManager extends Handler {
SubscriptionInfoInternal::getOnlyNonTerrestrialNetwork),
new AbstractMap.SimpleImmutableEntry<>(
SimInfo.COLUMN_SERVICE_CAPABILITIES,
- SubscriptionInfoInternal::getServiceCapabilities)
+ SubscriptionInfoInternal::getServiceCapabilities),
+ new AbstractMap.SimpleImmutableEntry<>(
+ SimInfo.COLUMN_TRANSFER_STATUS,
+ SubscriptionInfoInternal::getTransferStatus),
+ new AbstractMap.SimpleImmutableEntry<>(
+ SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS,
+ SubscriptionInfoInternal::getSatelliteEntitlementStatus),
+ new AbstractMap.SimpleImmutableEntry<>(
+ SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS,
+ SubscriptionInfoInternal::getSatelliteEntitlementPlmns)
);
/**
@@ -418,7 +427,13 @@ public class SubscriptionDatabaseManager extends Handler {
SubscriptionDatabaseManager::setNtn),
new AbstractMap.SimpleImmutableEntry<>(
SimInfo.COLUMN_SERVICE_CAPABILITIES,
- SubscriptionDatabaseManager::setServiceCapabilities)
+ SubscriptionDatabaseManager::setServiceCapabilities),
+ new AbstractMap.SimpleImmutableEntry<>(
+ SimInfo.COLUMN_TRANSFER_STATUS,
+ SubscriptionDatabaseManager::setTransferStatus),
+ new AbstractMap.SimpleImmutableEntry<>(
+ SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS,
+ SubscriptionDatabaseManager::setSatelliteEntitlementStatus)
);
/**
@@ -480,7 +495,10 @@ public class SubscriptionDatabaseManager extends Handler {
SubscriptionDatabaseManager::setNumberFromCarrier),
new AbstractMap.SimpleImmutableEntry<>(
SimInfo.COLUMN_PHONE_NUMBER_SOURCE_IMS,
- SubscriptionDatabaseManager::setNumberFromIms)
+ SubscriptionDatabaseManager::setNumberFromIms),
+ new AbstractMap.SimpleImmutableEntry<>(
+ SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS,
+ SubscriptionDatabaseManager::setSatelliteEntitlementPlmns)
);
/**
@@ -2099,6 +2117,51 @@ public class SubscriptionDatabaseManager extends Handler {
}
/**
+ * Set whether satellite entitlement status is enabled by entitlement query result.
+ *
+ * @param subId Subscription id.
+ * @param isSatelliteEntitlementStatus Whether satellite entitlement status is enabled or
+ * disabled.
+ * @throws IllegalArgumentException if the subscription does not exist.
+ */
+ public void setSatelliteEntitlementStatus(int subId,
+ int isSatelliteEntitlementStatus) {
+ writeDatabaseAndCacheHelper(subId,
+ SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS,
+ isSatelliteEntitlementStatus,
+ SubscriptionInfoInternal.Builder::setSatelliteEntitlementStatus);
+ }
+
+ /**
+ * Set satellite entitlement plmns by entitlement query result.
+ *
+ * @param subId Subscription id.
+ * @param satelliteEntitlementPlmns Satellite entitlement plmns
+ * @throws IllegalArgumentException if the subscription does not exist.
+ */
+ public void setSatelliteEntitlementPlmns(int subId,
+ @NonNull String satelliteEntitlementPlmns) {
+ writeDatabaseAndCacheHelper(subId,
+ SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS,
+ satelliteEntitlementPlmns,
+ SubscriptionInfoInternal.Builder::setSatelliteEntitlementPlmns);
+ }
+
+ /**
+ * Set satellite entitlement plmn list by entitlement query result.
+ *
+ * @param subId Subscription id.
+ * @param satelliteEntitlementPlmnList Satellite entitlement plmn list
+ * @throws IllegalArgumentException if the subscription does not exist.
+ */
+ public void setSatelliteEntitlementPlmnList(int subId,
+ @NonNull List<String> satelliteEntitlementPlmnList) {
+ String satelliteEntitlementPlmns = satelliteEntitlementPlmnList.stream().collect(
+ Collectors.joining(","));
+ setSatelliteEntitlementPlmns(subId, satelliteEntitlementPlmns);
+ }
+
+ /**
* Reload the database from content provider to the cache. This must be a synchronous operation
* to prevent cache/database out-of-sync. Callers should be cautious to call this method because
* it might take longer time to complete.
@@ -2330,11 +2393,21 @@ public class SubscriptionDatabaseManager extends Handler {
SimInfo.COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER)))
.setServiceCapabilities(cursor.getInt(
cursor.getColumnIndexOrThrow(
- SimInfo.COLUMN_SERVICE_CAPABILITIES)));
+ SimInfo.COLUMN_SERVICE_CAPABILITIES)))
+ .setSatelliteEntitlementStatus(cursor.getInt(
+ cursor.getColumnIndexOrThrow(
+ SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS)))
+ .setSatelliteEntitlementPlmns(cursor.getString(
+ cursor.getColumnIndexOrThrow(
+ SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS)));
if (mFeatureFlags.oemEnabledSatelliteFlag()) {
builder.setOnlyNonTerrestrialNetwork(cursor.getInt(cursor.getColumnIndexOrThrow(
SimInfo.COLUMN_IS_NTN)));
}
+ if (mFeatureFlags.supportPsimToEsimConversion()) {
+ builder.setTransferStatus(cursor.getInt(cursor.getColumnIndexOrThrow(
+ SimInfo.COLUMN_TRANSFER_STATUS)));
+ }
return builder.build();
}
@@ -2409,6 +2482,25 @@ public class SubscriptionDatabaseManager extends Handler {
}
/**
+ * Set the transfer status of the subscriptionInfo that corresponds to subId.
+ *
+ * @param subId Subscription ID.
+ * @param status The transfer status to change.
+ *
+ * @throws IllegalArgumentException if the subscription does not exist.
+ */
+ public void setTransferStatus(int subId, int status) {
+ if (!mFeatureFlags.supportPsimToEsimConversion()) {
+ log("SubscriptionDatabaseManager:supportPsimToEsimConversion is false");
+ return;
+ }
+
+ writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_TRANSFER_STATUS,
+ status,
+ SubscriptionInfoInternal.Builder::setTransferStatus);
+ }
+
+ /**
* Log debug messages.
*
* @param s debug messages
diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java b/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java
index c6fc23d8e9..82af4e80e4 100644
--- a/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java
+++ b/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java
@@ -444,7 +444,7 @@ public class SubscriptionInfoInternal {
/**
* Whether satellite attach for carrier is enabled or disabled by user.
- * By default, its disabled. It is intended to use integer to fit the database format.
+ * By default, its enabled. It is intended to use integer to fit the database format.
*/
private final int mIsSatelliteAttachEnabledForCarrier;
@@ -474,6 +474,23 @@ public class SubscriptionInfoInternal {
private final int mServiceCapabilities;
/**
+ * The transfer status of the subscription
+ */
+ private final int mTransferStatus;
+
+ /**
+ * Whether satellite entitlement status is enabled or disabled by the entitlement query result.
+ * By default, its disabled. It is intended to use integer to fit the database format.
+ */
+ private final int mIsSatelliteEntitlementStatus;
+
+ /**
+ * The satellite entitlement plmns based on the entitlement query results
+ * By default, its empty. It is intended to use string to fit the database format.
+ */
+ @NonNull private final String mSatelliteEntitlementPlmns;
+
+ /**
* Constructor from builder.
*
* @param builder Builder of {@link SubscriptionInfoInternal}.
@@ -549,6 +566,9 @@ public class SubscriptionInfoInternal {
this.mCardId = builder.mCardId;
this.mIsGroupDisabled = builder.mIsGroupDisabled;
this.mServiceCapabilities = builder.mServiceCapabilities;
+ this.mTransferStatus = builder.mTransferStatus;
+ this.mIsSatelliteEntitlementStatus = builder.mIsSatelliteEntitlementStatus;
+ this.mSatelliteEntitlementPlmns = builder.mSatelliteEntitlementPlmns;
}
/**
@@ -621,7 +641,8 @@ public class SubscriptionInfoInternal {
* @return the number of this subscription.
*/
public String getNumber() {
- return mNumber;
+ if (TextUtils.isEmpty(mNumberFromCarrier)) return mNumber;
+ return mNumberFromCarrier;
}
/**
@@ -1205,6 +1226,29 @@ public class SubscriptionInfoInternal {
public int getServiceCapabilities() {
return mServiceCapabilities;
}
+ /**
+ * @return Transfer status.
+ */
+ public int getTransferStatus() {
+ return mTransferStatus;
+ }
+
+ /**
+ * @return {@code 1} if satellite entitlement status is enabled by entitlement query result.
+ */
+ public int getSatelliteEntitlementStatus() {
+ return mIsSatelliteEntitlementStatus;
+ }
+
+ /**
+ * @return Satellite entitlement plmns is empty or not by entitlement query result.
+ *
+ * For example, "123123, 12310" or ""
+ */
+ @NonNull
+ public String getSatelliteEntitlementPlmns() {
+ return mSatelliteEntitlementPlmns;
+ }
/** @return converted {@link SubscriptionInfo}. */
@NonNull
@@ -1217,7 +1261,7 @@ public class SubscriptionInfoInternal {
.setCarrierName(mCarrierName)
.setDisplayNameSource(mDisplayNameSource)
.setIconTint(mIconTint)
- .setNumber(mNumber)
+ .setNumber(getNumber())
.setDataRoaming(mDataRoaming)
.setMcc(mMcc)
.setMnc(mMnc)
@@ -1244,6 +1288,7 @@ public class SubscriptionInfoInternal {
.setOnlyNonTerrestrialNetwork(mIsOnlyNonTerrestrialNetwork == 1)
.setServiceCapabilities(
SubscriptionManager.getServiceCapabilitiesSet(mServiceCapabilities))
+ .setTransferStatus(mTransferStatus)
.build();
}
@@ -1264,7 +1309,7 @@ public class SubscriptionInfoInternal {
+ " displayNameSource="
+ SubscriptionManager.displayNameSourceToString(mDisplayNameSource)
+ " iconTint=" + mIconTint
- + " number=" + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mNumber)
+ + " number=" + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, getNumber())
+ " dataRoaming=" + mDataRoaming
+ " mcc=" + mMcc
+ " mnc=" + mMnc
@@ -1304,6 +1349,9 @@ public class SubscriptionInfoInternal {
+ " getOnlyNonTerrestrialNetwork=" + mIsOnlyNonTerrestrialNetwork
+ " isGroupDisabled=" + mIsGroupDisabled
+ " serviceCapabilities=" + mServiceCapabilities
+ + " transferStatus=" + mTransferStatus
+ + " satelliteEntitlementStatus=" + mIsSatelliteEntitlementStatus
+ + " satelliteEntitlementPlmns=" + mSatelliteEntitlementPlmns
+ "]";
}
@@ -1361,7 +1409,10 @@ public class SubscriptionInfoInternal {
that.mNumberFromCarrier) && mNumberFromIms.equals(that.mNumberFromIms)
&& mIsSatelliteAttachEnabledForCarrier == that.mIsSatelliteAttachEnabledForCarrier
&& mIsOnlyNonTerrestrialNetwork == that.mIsOnlyNonTerrestrialNetwork
- && mServiceCapabilities == that.mServiceCapabilities;
+ && mServiceCapabilities == that.mServiceCapabilities
+ && mTransferStatus == that.mTransferStatus
+ && mIsSatelliteEntitlementStatus == that.mIsSatelliteEntitlementStatus
+ && mSatelliteEntitlementPlmns == that.mSatelliteEntitlementPlmns;
}
@Override
@@ -1384,7 +1435,8 @@ public class SubscriptionInfoInternal {
mNumberFromIms, mPortIndex, mUsageSetting, mLastUsedTPMessageReference, mUserId,
mIsSatelliteEnabled, mCardId, mIsGroupDisabled,
mIsSatelliteAttachEnabledForCarrier, mIsOnlyNonTerrestrialNetwork,
- mServiceCapabilities);
+ mServiceCapabilities, mTransferStatus, mIsSatelliteEntitlementStatus,
+ mSatelliteEntitlementPlmns);
result = 31 * result + Arrays.hashCode(mNativeAccessRules);
result = 31 * result + Arrays.hashCode(mCarrierConfigAccessRules);
result = 31 * result + Arrays.hashCode(mRcsConfig);
@@ -1750,7 +1802,7 @@ public class SubscriptionInfoInternal {
/**
* Whether satellite attach for carrier is enabled by user.
*/
- private int mIsSatelliteAttachEnabledForCarrier = 0;
+ private int mIsSatelliteAttachEnabledForCarrier = 1;
/**
* Whether this subscription is used for communicating with non-terrestrial network or not.
@@ -1777,6 +1829,22 @@ public class SubscriptionInfoInternal {
private int mServiceCapabilities;
/**
+ * The transfer status of the subscription
+ */
+ private int mTransferStatus;
+
+ /**
+ * Whether satellite entitlement status is enabled by entitlement query result.
+ */
+ private int mIsSatelliteEntitlementStatus = 0;
+
+ /**
+ * Whether satellite entitlement plmns is empty or not by entitlement query result.
+ */
+ @NonNull
+ private String mSatelliteEntitlementPlmns = "";
+
+ /**
* Default constructor.
*/
public Builder() {
@@ -1855,6 +1923,9 @@ public class SubscriptionInfoInternal {
mCardId = info.mCardId;
mIsGroupDisabled = info.mIsGroupDisabled;
mServiceCapabilities = info.mServiceCapabilities;
+ mTransferStatus = info.mTransferStatus;
+ mIsSatelliteEntitlementStatus = info.mIsSatelliteEntitlementStatus;
+ mSatelliteEntitlementPlmns = info.mSatelliteEntitlementPlmns;
}
/**
@@ -2787,6 +2858,44 @@ public class SubscriptionInfoInternal {
}
/**
+ * Set the transfer status of the subscription.
+ *
+ * @param status The transfer status
+ * @return The builder.
+ */
+ @NonNull
+ public Builder setTransferStatus(int status) {
+ mTransferStatus = status;
+ return this;
+ }
+
+ /**
+ * Set whether satellite entitlement status is enabled by entitlement query result.
+ *
+ * @param isSatelliteEntitlementStatus {@code 1} if satellite entitlement status is
+ * enabled by entitlement query result.
+ * @return The builder
+ */
+ @NonNull
+ public Builder setSatelliteEntitlementStatus(int isSatelliteEntitlementStatus) {
+ mIsSatelliteEntitlementStatus = isSatelliteEntitlementStatus;
+ return this;
+ }
+
+ /**
+ * Set whether satellite entitlement plmns is empty or not by entitlement query result.
+ *
+ * @param satelliteEntitlementPlmns satellite entitlement plmns is empty or not by
+ * entitlement query result.
+ * @return The builder
+ */
+ @NonNull
+ public Builder setSatelliteEntitlementPlmns(@NonNull String satelliteEntitlementPlmns) {
+ mSatelliteEntitlementPlmns = satelliteEntitlementPlmns;
+ return this;
+ }
+
+ /**
* Build the {@link SubscriptionInfoInternal}.
*
* @return The {@link SubscriptionInfoInternal} instance.
diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java b/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
index ddf80a8cb6..8757c9791a 100644
--- a/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
+++ b/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
@@ -22,6 +22,7 @@ import static android.telephony.TelephonyManager.ENABLE_FEATURE_MAPPING;
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.ColorInt;
+import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -186,7 +187,9 @@ public class SubscriptionManagerService extends ISub.Stub {
SimInfo.COLUMN_NR_ADVANCED_CALLING_ENABLED,
SimInfo.COLUMN_SATELLITE_ENABLED,
SimInfo.COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER,
- SimInfo.COLUMN_IS_NTN
+ SimInfo.COLUMN_IS_NTN,
+ SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS,
+ SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS
);
/**
@@ -922,6 +925,26 @@ public class SubscriptionManagerService extends ISub.Stub {
}
/**
+ * Set the group owner on the subscription
+ *
+ * <p> Note: This only sets the group owner field and doesn't update other relevant fields.
+ * Prefer to call {@link #addSubscriptionsIntoGroup}.
+ *
+ * @param subId Subscription id.
+ * @param groupOwner The group owner to assign to the subscription
+ */
+ public void setGroupOwner(int subId, @NonNull String groupOwner) {
+ // This can throw IllegalArgumentException if the subscription does not exist.
+ try {
+ mSubscriptionDatabaseManager.setGroupOwner(
+ subId,
+ groupOwner);
+ } catch (IllegalArgumentException e) {
+ loge("setManaged: invalid subId=" + subId);
+ }
+ }
+
+ /**
* Set last used TP message reference.
*
* @param subId Subscription id.
@@ -1219,7 +1242,11 @@ public class SubscriptionManagerService extends ISub.Stub {
builder.setCardString(mUiccController.convertToCardString(cardId));
}
+ if (mFeatureFlags.supportPsimToEsimConversion()) {
+ builder.setTransferStatus(subInfo.getTransferStatus());
+ }
embeddedSubs.add(subInfo.getSubscriptionId());
+
subInfo = builder.build();
log("updateEmbeddedSubscriptions: update subscription " + subInfo);
mSubscriptionDatabaseManager.updateSubscription(subInfo);
@@ -2161,6 +2188,7 @@ public class SubscriptionManagerService extends ISub.Stub {
* @return 0 if success, < 0 on error
*
* @throws SecurityException if the caller does not have required permissions.
+ * @throws IllegalArgumentException if {@code slotIndex} is invalid.
*/
@Override
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@@ -2174,6 +2202,11 @@ public class SubscriptionManagerService extends ISub.Stub {
enforceTelephonyFeatureWithException(getCurrentPackageName(), "addSubInfo");
+ if (!SubscriptionManager.isValidSlotIndex(slotIndex)
+ && slotIndex != SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+ throw new IllegalArgumentException("Invalid slotIndex " + slotIndex);
+ }
+
// Now that all security checks passes, perform the operation as ourselves.
final long identity = Binder.clearCallingIdentity();
try {
@@ -3911,12 +3944,31 @@ public class SubscriptionManagerService extends ISub.Stub {
}
/**
+ * Returns whether the given subscription is associated with the calling user.
+ *
+ * @param subscriptionId the subscription ID of the subscription
+ * @return {@code true} if the subscription is associated with the user that the calling process
+ * is running in; {@code false} otherwise.
+ *
+ * @throws IllegalArgumentException if subscription doesn't exist.
+ * @throws SecurityException if the caller doesn't have permissions required.
+ */
+ @Override
+ public boolean isSubscriptionAssociatedWithCallingUser(int subscriptionId) {
+ enforcePermissions("isSubscriptionAssociatedWithCallingUser",
+ Manifest.permission.READ_PHONE_STATE);
+
+ UserHandle myUserHandle = UserHandle.of(UserHandle.getCallingUserId());
+ return mFeatureFlags.subscriptionUserAssociationQuery()
+ && isSubscriptionAssociatedWithUserNoCheck(subscriptionId, myUserHandle);
+ }
+
+ /**
* Check if subscription and user are associated with each other.
*
* @param subscriptionId the subId of the subscription
* @param userHandle user handle of the user
* @return {@code true} if subscription is associated with user
- * {@code true} if there are no subscriptions on device
* else {@code false} if subscription is not associated with user.
*
* @throws SecurityException if the caller doesn't have permissions required.
@@ -3927,6 +3979,12 @@ public class SubscriptionManagerService extends ISub.Stub {
@NonNull UserHandle userHandle) {
enforcePermissions("isSubscriptionAssociatedWithUser",
Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION);
+
+ return isSubscriptionAssociatedWithUserNoCheck(subscriptionId, userHandle);
+ }
+
+ private boolean isSubscriptionAssociatedWithUserNoCheck(int subscriptionId,
+ @NonNull UserHandle userHandle) {
SubscriptionInfoInternal subInfoInternal = mSubscriptionDatabaseManager
.getSubscriptionInfoInternal(subscriptionId);
// Throw IAE if no record of the sub's association state.
@@ -4299,6 +4357,70 @@ public class SubscriptionManagerService extends ISub.Stub {
}
}
+
+
+ /**
+ * Set the transfer status of the subscriptionInfo that corresponds to subId.
+ * @param subId The unique SubscriptionInfo key in database.
+ * @param status The transfer status to change. This value must be one of the following.
+ * {@link SubscriptionManager#TRANSFER_STATUS_NONE},
+ * {@link SubscriptionManager#TRANSFER_STATUS_TRANSFERRED_OUT} or
+ * {@link SubscriptionManager#TRANSFER_STATUS_CONVERTED}
+ *
+ */
+ @Override
+ @EnforcePermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+ public void setTransferStatus(int subId, int status) {
+ setTransferStatus_enforcePermission();
+ if (mContext.checkCallingOrSelfPermission(
+ Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Must have WRITE_EMBEDDED_SUBSCRIPTIONS to"
+ + "setTransferStatus");
+ }
+ long token = Binder.clearCallingIdentity();
+ try {
+ mSubscriptionDatabaseManager.setTransferStatus(subId, status);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Set the satellite entitlement plmn list value in the subscription database.
+ *
+ * @param subId subscription id.
+ * @param satelliteEntitlementPlmnList satellite entitlement plmn list
+ */
+ public void setSatelliteEntitlementPlmnList(int subId,
+ @NonNull List<String> satelliteEntitlementPlmnList) {
+ try {
+ mSubscriptionDatabaseManager.setSatelliteEntitlementPlmnList(
+ subId, satelliteEntitlementPlmnList);
+ } catch (IllegalArgumentException e) {
+ loge("setSatelliteEntitlementPlmnList: invalid subId=" + subId);
+ }
+ }
+
+ /**
+ * Get the satellite entitlement plmn list value from the subscription database.
+ *
+ * @param subId subscription id.
+ * @return satellite entitlement plmn list
+ */
+ @NonNull
+ public List<String> getSatelliteEntitlementPlmnList(int subId) {
+ SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager.getSubscriptionInfoInternal(
+ subId);
+ if (subInfo == null) {
+ loge("getSatelliteEntitlementPlmnList: invalid subId=" + subId);
+ return new ArrayList<>();
+ }
+
+ return Arrays.stream(subInfo.getSatelliteEntitlementPlmns().split(",")).collect(
+ Collectors.toList());
+ }
+
/**
* Get the current calling package name.
* @return the current calling package name