diff options
Diffstat (limited to 'src')
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 |