diff options
Diffstat (limited to 'src/java/com/android/internal/telephony')
38 files changed, 2174 insertions, 490 deletions
diff --git a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java index 5517bc6f4e..9113514c75 100644 --- a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java +++ b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java @@ -21,6 +21,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.os.AsyncResult; import android.os.Build; import android.os.Bundle; @@ -159,6 +160,12 @@ public class GsmCdmaCallTracker extends CallTracker { public GsmCdmaCallTracker(GsmCdmaPhone phone, FeatureFlags featureFlags) { super(featureFlags); + if (mFeatureFlags.minimalTelephonyCdmCheck() + && !phone.getContext().getPackageManager().hasSystemFeature( + PackageManager.FEATURE_TELEPHONY_CALLING)) { + throw new UnsupportedOperationException("GsmCdmaCallTracker requires calling"); + } + this.mPhone = phone; mCi = phone.mCi; mCi.registerForCallStateChanged(this, EVENT_CALL_STATE_CHANGE, null); @@ -1492,7 +1499,7 @@ public class GsmCdmaCallTracker extends CallTracker { switch (msg.what) { case EVENT_POLL_CALLS_RESULT: - Rlog.d(LOG_TAG, "Event EVENT_POLL_CALLS_RESULT Received"); + if (DBG_POLL) Rlog.d(LOG_TAG, "Event EVENT_POLL_CALLS_RESULT Received"); if (msg == mLastRelevantPoll) { if (DBG_POLL) log( diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java index aca759b703..de7ebd6514 100644 --- a/src/java/com/android/internal/telephony/GsmCdmaPhone.java +++ b/src/java/com/android/internal/telephony/GsmCdmaPhone.java @@ -42,6 +42,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.database.SQLException; import android.hardware.radio.modem.ImeiInfo; import android.net.Uri; @@ -370,9 +371,11 @@ public class GsmCdmaPhone extends Phone { SignalStrengthController.class.getName()).makeSignalStrengthController(this); mSST = mTelephonyComponentFactory.inject(ServiceStateTracker.class.getName()) .makeServiceStateTracker(this, this.mCi, featureFlags); - mEmergencyNumberTracker = mTelephonyComponentFactory - .inject(EmergencyNumberTracker.class.getName()).makeEmergencyNumberTracker( - this, this.mCi); + if (hasCalling()) { + mEmergencyNumberTracker = mTelephonyComponentFactory + .inject(EmergencyNumberTracker.class.getName()).makeEmergencyNumberTracker( + this, this.mCi, mFeatureFlags); + } mDeviceStateMonitor = mTelephonyComponentFactory.inject(DeviceStateMonitor.class.getName()) .makeDeviceStateMonitor(this, mFeatureFlags); @@ -412,9 +415,11 @@ public class GsmCdmaPhone extends Phone { mCallWaitingController = new CallWaitingController(this); - loadTtyMode(); + if (hasCalling()) { + loadTtyMode(); - CallManager.getInstance().registerPhone(this); + CallManager.getInstance().registerPhone(this); + } mSubscriptionsChangedListener = new SubscriptionManager.OnSubscriptionsChangedListener() { @@ -464,13 +469,21 @@ public class GsmCdmaPhone extends Phone { } }; + private boolean hasCalling() { + if (!mFeatureFlags.minimalTelephonyCdmCheck()) return true; + return mContext.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_TELEPHONY_CALLING); + } + private void initOnce(CommandsInterface ci) { if (ci instanceof SimulatedRadioControl) { mSimulatedRadioControl = (SimulatedRadioControl) ci; } - mCT = mTelephonyComponentFactory.inject(GsmCdmaCallTracker.class.getName()) - .makeGsmCdmaCallTracker(this, mFeatureFlags); + if (hasCalling()) { + mCT = mTelephonyComponentFactory.inject(GsmCdmaCallTracker.class.getName()) + .makeGsmCdmaCallTracker(this, mFeatureFlags); + } mIccPhoneBookIntManager = mTelephonyComponentFactory .inject(IccPhoneBookInterfaceManager.class.getName()) .makeIccPhoneBookInterfaceManager(this); @@ -558,7 +571,7 @@ public class GsmCdmaPhone extends Phone { mNullCipherNotifier = mTelephonyComponentFactory .inject(NullCipherNotifier.class.getName()) - .makeNullCipherNotifier(); + .makeNullCipherNotifier(mSafetySource); mCi.registerForSecurityAlgorithmUpdates( this, EVENT_SECURITY_ALGORITHM_UPDATE, null); } @@ -693,7 +706,7 @@ public class GsmCdmaPhone extends Phone { unregisterForIccRecordEvents(); registerForIccRecordEvents(); - mCT.updatePhoneType(); + if (mCT != null) mCT.updatePhoneType(); int radioState = mCi.getRadioState(); if (radioState != TelephonyManager.RADIO_POWER_UNAVAILABLE) { @@ -753,6 +766,8 @@ public class GsmCdmaPhone extends Phone { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @Override public PhoneConstants.State getState() { + if (!hasCalling()) return PhoneConstants.State.IDLE; + if (mImsPhone != null) { PhoneConstants.State imsState = mImsPhone.getState(); if (imsState != PhoneConstants.State.IDLE) { @@ -837,6 +852,7 @@ public class GsmCdmaPhone extends Phone { @Override public boolean isDataSuspended() { + if (mCT == null) return false; return mCT.mState != PhoneConstants.State.IDLE && !mSST.isConcurrentVoiceAndDataAllowed(); } @@ -884,7 +900,7 @@ public class GsmCdmaPhone extends Phone { @Override public boolean isInEmergencyCall() { - if (isPhoneTypeGsm()) { + if (!hasCalling() || isPhoneTypeGsm()) { return false; } else { return mCT.isInEmergencyCall(); @@ -893,7 +909,7 @@ public class GsmCdmaPhone extends Phone { @Override protected void setIsInEmergencyCall() { - if (!isPhoneTypeGsm()) { + if (!hasCalling() && !isPhoneTypeGsm()) { mCT.setIsInEmergencyCall(); } } @@ -985,6 +1001,7 @@ public class GsmCdmaPhone extends Phone { @Override public void acceptCall(int videoState) throws CallStateException { + if (!hasCalling()) throw new CallStateException(); Phone imsPhone = mImsPhone; if ( imsPhone != null && imsPhone.getRingingCall().isRinging() ) { imsPhone.acceptCall(videoState); @@ -995,6 +1012,7 @@ public class GsmCdmaPhone extends Phone { @Override public void rejectCall() throws CallStateException { + if (!hasCalling()) throw new CallStateException(); mCT.rejectCall(); } @@ -1025,6 +1043,7 @@ public class GsmCdmaPhone extends Phone { @Override public boolean canConference() { + if (!hasCalling()) return false; if (mImsPhone != null && mImsPhone.canConference()) { return true; } @@ -1075,12 +1094,13 @@ public class GsmCdmaPhone extends Phone { @Override public void clearDisconnected() { + if (!hasCalling()) return; mCT.clearDisconnected(); } @Override public boolean canTransfer() { - if (isPhoneTypeGsm()) { + if (hasCalling() && isPhoneTypeGsm()) { return mCT.canTransfer(); } else { loge("canTransfer: not possible in CDMA"); @@ -1090,7 +1110,7 @@ public class GsmCdmaPhone extends Phone { @Override public void explicitCallTransfer() { - if (isPhoneTypeGsm()) { + if (hasCalling() && isPhoneTypeGsm()) { mCT.explicitCallTransfer(); } else { loge("explicitCallTransfer: not possible in CDMA"); @@ -1104,11 +1124,13 @@ public class GsmCdmaPhone extends Phone { @Override public GsmCdmaCall getBackgroundCall() { + if (!hasCalling()) return null; return mCT.mBackgroundCall; } @Override public Call getRingingCall() { + if (!hasCalling()) return null; Phone imsPhone = mImsPhone; // It returns the ringing call of ImsPhone if the ringing call of GSMPhone isn't ringing. // In CallManager.registerPhone(), it always registers ringing call of ImsPhone, because @@ -1184,7 +1206,7 @@ public class GsmCdmaPhone extends Phone { private boolean handleCallDeflectionIncallSupplementaryService( String dialString) { - if (dialString.length() > 1) { + if (!hasCalling() || dialString.length() > 1) { return false; } @@ -1209,7 +1231,7 @@ public class GsmCdmaPhone extends Phone { private boolean handleCallWaitingIncallSupplementaryService(String dialString) { int len = dialString.length(); - if (len > 2) { + if (!hasCalling() || len > 2) { return false; } @@ -1429,6 +1451,9 @@ public class GsmCdmaPhone extends Phone { @Override public Connection dial(String dialString, @NonNull DialArgs dialArgs, Consumer<Phone> chosenPhoneConsumer) throws CallStateException { + if (!hasCalling()) { + throw new CallStateException("Calling feature is not supported!"); + } if (!isPhoneTypeGsm() && dialArgs.uusInfo != null) { throw new CallStateException("Sending UUS information NOT supported in CDMA!"); } @@ -2148,7 +2173,9 @@ public class GsmCdmaPhone extends Phone { @Override public int getEmergencyNumberDbVersion() { - return getEmergencyNumberTracker().getEmergencyNumberDbVersion(); + EmergencyNumberTracker tracker = getEmergencyNumberTracker(); + if (tracker == null) return -1; + return tracker.getEmergencyNumberDbVersion(); } @Override @@ -3134,6 +3161,8 @@ public class GsmCdmaPhone extends Phone { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private void syncClirSetting() { + if (!hasCalling()) return; + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); migrateClirSettingIfNeeded(sp); @@ -3339,8 +3368,10 @@ public class GsmCdmaPhone extends Phone { if (b != null) { updateBroadcastEmergencyCallStateChangesAfterCarrierConfigChanged(b); updateCdmaRoamingSettingsAfterCarrierConfigChanged(b); - updateNrSettingsAfterCarrierConfigChanged(b); - updateVoNrSettings(b); + if (hasCalling()) { + updateNrSettingsAfterCarrierConfigChanged(b); + updateVoNrSettings(b); + } updateSsOverCdmaSupported(b); updateCarrierN1ModeSupported(b); } else { @@ -3738,7 +3769,7 @@ public class GsmCdmaPhone extends Phone { && mNullCipherNotifier != null) { ar = (AsyncResult) msg.obj; SecurityAlgorithmUpdate update = (SecurityAlgorithmUpdate) ar.result; - mNullCipherNotifier.onSecurityAlgorithmUpdate(getPhoneId(), update); + mNullCipherNotifier.onSecurityAlgorithmUpdate(mContext, getSubId(), update); } break; @@ -4912,6 +4943,7 @@ public class GsmCdmaPhone extends Phone { * Handler of RIL Voice Radio Technology changed event. */ private void onVoiceRegStateOrRatChanged(int vrs, int vrat) { + if (!hasCalling()) return; logd("onVoiceRegStateOrRatChanged"); mCT.dispatchCsCallRadioTech(getCsCallRadioTech(vrs, vrat)); } @@ -5113,6 +5145,8 @@ public class GsmCdmaPhone extends Phone { * Load the current TTY mode in GsmCdmaPhone based on Telecom and UI settings. */ private void loadTtyMode() { + if (!hasCalling()) return; + int ttyMode = TelecomManager.TTY_MODE_OFF; TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class); if (telecomManager != null) { @@ -5392,9 +5426,9 @@ public class GsmCdmaPhone extends Phone { // enable/disable API. if (mFeatureFlags.enableModemCipherTransparencyUnsolEvents()) { if (prefEnabled) { - mNullCipherNotifier.enable(); + mNullCipherNotifier.enable(mContext); } else { - mNullCipherNotifier.disable(); + mNullCipherNotifier.disable(mContext); } } else { logi( diff --git a/src/java/com/android/internal/telephony/ImsSmsDispatcher.java b/src/java/com/android/internal/telephony/ImsSmsDispatcher.java index 4e4d55d34b..4146c245dc 100644 --- a/src/java/com/android/internal/telephony/ImsSmsDispatcher.java +++ b/src/java/com/android/internal/telephony/ImsSmsDispatcher.java @@ -203,12 +203,12 @@ public class ImsSmsDispatcher extends SMSDispatcher { mTrackers.remove(token); mPhone.notifySmsSent(tracker.mDestAddress); mSmsDispatchersController.notifySmsSentToEmergencyStateTracker( - tracker.mDestAddress, tracker.mMessageId); + tracker.mDestAddress, tracker.mMessageId, true); break; case ImsSmsImplBase.SEND_STATUS_ERROR: tracker.onFailed(mContext, reason, networkReasonCode); mTrackers.remove(token); - notifySmsSentFailedToEmergencyStateTracker(tracker); + notifySmsSentFailedToEmergencyStateTracker(tracker, true); break; case ImsSmsImplBase.SEND_STATUS_ERROR_RETRY: int maxRetryCountOverIms = getMaxRetryCountOverIms(); @@ -227,7 +227,7 @@ public class ImsSmsDispatcher extends SMSDispatcher { } else { tracker.onFailed(mContext, reason, networkReasonCode); mTrackers.remove(token); - notifySmsSentFailedToEmergencyStateTracker(tracker); + notifySmsSentFailedToEmergencyStateTracker(tracker, true); } break; case ImsSmsImplBase.SEND_STATUS_ERROR_FALLBACK: @@ -304,6 +304,11 @@ public class ImsSmsDispatcher extends SMSDispatcher { switch (result) { case Intents.RESULT_SMS_HANDLED: mappedResult = ImsSmsImplBase.DELIVER_STATUS_OK; + if (message != null) { + mSmsDispatchersController + .notifySmsReceivedViaImsToEmergencyStateTracker( + message.getOriginatingAddress()); + } break; case Intents.RESULT_SMS_OUT_OF_MEMORY: mappedResult = ImsSmsImplBase.DELIVER_STATUS_ERROR_NO_MEMORY; diff --git a/src/java/com/android/internal/telephony/MultiSimSettingController.java b/src/java/com/android/internal/telephony/MultiSimSettingController.java index 8488ab0359..aaeba23622 100644 --- a/src/java/com/android/internal/telephony/MultiSimSettingController.java +++ b/src/java/com/android/internal/telephony/MultiSimSettingController.java @@ -35,6 +35,7 @@ import android.annotation.NonNull; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.os.AsyncResult; import android.os.Handler; import android.os.Looper; @@ -52,6 +53,7 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback; +import com.android.internal.telephony.flags.FeatureFlags; import com.android.internal.telephony.subscription.SubscriptionInfoInternal; import com.android.internal.telephony.subscription.SubscriptionManagerService; import com.android.internal.telephony.util.ArrayUtils; @@ -122,6 +124,7 @@ public class MultiSimSettingController extends Handler { protected final Context mContext; private final SubscriptionManagerService mSubscriptionManagerService; + private final @NonNull FeatureFlags mFeatureFlags; // Keep a record of active primary (non-opportunistic) subscription list. @NonNull private List<Integer> mPrimarySubList = new ArrayList<>(); @@ -201,10 +204,11 @@ public class MultiSimSettingController extends Handler { /** * Init instance of MultiSimSettingController. */ - public static MultiSimSettingController init(Context context) { + public static MultiSimSettingController init(Context context, + @NonNull FeatureFlags featureFlags) { synchronized (MultiSimSettingController.class) { if (sInstance == null) { - sInstance = new MultiSimSettingController(context); + sInstance = new MultiSimSettingController(context, featureFlags); } else { Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance); } @@ -213,9 +217,10 @@ public class MultiSimSettingController extends Handler { } @VisibleForTesting - public MultiSimSettingController(Context context) { + public MultiSimSettingController(Context context, @NonNull FeatureFlags featureFlags) { mContext = context; mSubscriptionManagerService = SubscriptionManagerService.getInstance(); + mFeatureFlags = featureFlags; // Initialize mCarrierConfigLoadedSubIds and register to listen to carrier config change. TelephonyManager telephonyManager = ((TelephonyManager) mContext.getSystemService( @@ -239,6 +244,24 @@ public class MultiSimSettingController extends Handler { onCarrierConfigChanged(slotIndex, subId)); } + private boolean hasCalling() { + if (!mFeatureFlags.minimalTelephonyCdmCheck()) return true; + return mContext.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_TELEPHONY_CALLING); + } + + private boolean hasData() { + if (!mFeatureFlags.minimalTelephonyCdmCheck()) return true; + return mContext.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_TELEPHONY_DATA); + } + + private boolean hasMessaging() { + if (!mFeatureFlags.minimalTelephonyCdmCheck()) return true; + return mContext.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_TELEPHONY_MESSAGING); + } + /** * Notify MOBILE_DATA of a subscription is changed. */ @@ -606,35 +629,43 @@ public class MultiSimSettingController extends Handler { || mActiveModemCount == 1)) { int subId = mPrimarySubList.get(0); if (DBG) log("updateDefaultValues: to only primary sub " + subId); - mSubscriptionManagerService.setDefaultDataSubId(subId); - mSubscriptionManagerService.setDefaultVoiceSubId(subId); - mSubscriptionManagerService.setDefaultSmsSubId(subId); + if (hasData()) mSubscriptionManagerService.setDefaultDataSubId(subId); + if (hasCalling()) mSubscriptionManagerService.setDefaultVoiceSubId(subId); + if (hasMessaging()) mSubscriptionManagerService.setDefaultSmsSubId(subId); sendDefaultSubConfirmedNotification(subId); return; } if (DBG) log("updateDefaultValues: records: " + mPrimarySubList); - boolean dataSelected, voiceSelected, smsSelected; + boolean dataSelected = false; + boolean voiceSelected = false; + boolean smsSelected = false; - // Update default data subscription. - if (DBG) log("updateDefaultValues: Update default data subscription"); - dataSelected = updateDefaultValue(mPrimarySubList, - mSubscriptionManagerService.getDefaultDataSubId(), - mSubscriptionManagerService::setDefaultDataSubId); + if (hasData()) { + // Update default data subscription. + if (DBG) log("updateDefaultValues: Update default data subscription"); + dataSelected = updateDefaultValue(mPrimarySubList, + mSubscriptionManagerService.getDefaultDataSubId(), + mSubscriptionManagerService::setDefaultDataSubId); + } - // Update default voice subscription. - if (DBG) log("updateDefaultValues: Update default voice subscription"); - voiceSelected = updateDefaultValue(mPrimarySubList, - mSubscriptionManagerService.getDefaultVoiceSubId(), - mSubscriptionManagerService::setDefaultVoiceSubId); + if (hasCalling()) { + // Update default voice subscription. + if (DBG) log("updateDefaultValues: Update default voice subscription"); + voiceSelected = updateDefaultValue(mPrimarySubList, + mSubscriptionManagerService.getDefaultVoiceSubId(), + mSubscriptionManagerService::setDefaultVoiceSubId); + } - // Update default sms subscription. - if (DBG) log("updateDefaultValues: Update default sms subscription"); - smsSelected = updateDefaultValue(mPrimarySubList, - mSubscriptionManagerService.getDefaultSmsSubId(), - mSubscriptionManagerService::setDefaultSmsSubId, - mIsAskEverytimeSupportedForSms); + if (hasMessaging()) { + // Update default sms subscription. + if (DBG) log("updateDefaultValues: Update default sms subscription"); + smsSelected = updateDefaultValue(mPrimarySubList, + mSubscriptionManagerService.getDefaultSmsSubId(), + mSubscriptionManagerService::setDefaultSmsSubId, + mIsAskEverytimeSupportedForSms); + } boolean autoFallbackEnabled = mContext.getResources().getBoolean( com.android.internal.R.bool.config_voice_data_sms_auto_fallback); @@ -1023,11 +1054,11 @@ public class MultiSimSettingController extends Handler { int autoDefaultSubId = primarySubList.get(0); - if ((primarySubList.size() == 1) && !smsSelected) { + if (hasMessaging() && (primarySubList.size() == 1) && !smsSelected) { mSubscriptionManagerService.setDefaultSmsSubId(autoDefaultSubId); } - if ((primarySubList.size() == 1) && !voiceSelected) { + if (hasCalling() && (primarySubList.size() == 1) && !voiceSelected) { mSubscriptionManagerService.setDefaultVoiceSubId(autoDefaultSubId); } @@ -1036,13 +1067,15 @@ public class MultiSimSettingController extends Handler { log("User pref subId = " + userPrefDataSubId + " current dds " + defaultDataSubId + " next active subId " + autoDefaultSubId); - // If earlier user selected DDS is now available, set that as DDS subId. - if (primarySubList.contains(userPrefDataSubId) - && SubscriptionManager.isValidSubscriptionId(userPrefDataSubId) - && (defaultDataSubId != userPrefDataSubId)) { - mSubscriptionManagerService.setDefaultDataSubId(userPrefDataSubId); - } else if (!dataSelected) { - mSubscriptionManagerService.setDefaultDataSubId(autoDefaultSubId); + if (hasData()) { + // If earlier user selected DDS is now available, set that as DDS subId. + if (primarySubList.contains(userPrefDataSubId) + && SubscriptionManager.isValidSubscriptionId(userPrefDataSubId) + && (defaultDataSubId != userPrefDataSubId)) { + mSubscriptionManagerService.setDefaultDataSubId(userPrefDataSubId); + } else if (!dataSelected) { + mSubscriptionManagerService.setDefaultDataSubId(autoDefaultSubId); + } } if (DBG) { diff --git a/src/java/com/android/internal/telephony/NetworkTypeController.java b/src/java/com/android/internal/telephony/NetworkTypeController.java index b9ad388bbf..67ca1e1aaf 100644 --- a/src/java/com/android/internal/telephony/NetworkTypeController.java +++ b/src/java/com/android/internal/telephony/NetworkTypeController.java @@ -26,6 +26,7 @@ import android.os.AsyncResult; import android.os.Message; import android.os.PersistableBundle; import android.os.PowerManager; +import android.os.SystemClock; import android.telephony.AccessNetworkConstants; import android.telephony.Annotation; import android.telephony.CarrierConfigManager; @@ -194,6 +195,7 @@ public class NetworkTypeController extends StateMachine { private boolean mIsPhysicalChannelConfigOn; private boolean mIsPrimaryTimerActive; private boolean mIsSecondaryTimerActive; + private long mSecondaryTimerExpireTimestamp; private boolean mIsTimerResetEnabledForLegacyStateRrcIdle; /** Carrier config to reset timers when mccmnc changes */ private boolean mIsTimerResetEnabledOnPlmnChanges; @@ -220,6 +222,7 @@ public class NetworkTypeController extends StateMachine { // Cached copies below to prevent race conditions @NonNull private ServiceState mServiceState; + /** Used to track link status to be DORMANT or ACTIVE */ @Nullable private List<PhysicalChannelConfig> mPhysicalChannelConfigs; // Ratchet physical channel config fields to prevent 5G/5G+ flickering @@ -378,6 +381,9 @@ public class NetworkTypeController extends StateMachine { createTimerRules(nrIconConfiguration, overrideTimerRule, overrideSecondaryTimerRule); updatePhysicalChannelConfigs( mPhone.getServiceStateTracker().getPhysicalChannelConfigList()); + if (isUsingPhysicalChannelConfigForRrcDetection()) { + mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig(); + } } private void createTimerRules(String icons, String timers, String secondaryTimers) { @@ -663,6 +669,7 @@ public class NetworkTypeController extends StateMachine { case EVENT_SECONDARY_TIMER_EXPIRED: if (DBG) log("Secondary timer expired for state: " + mSecondaryTimerState); mIsSecondaryTimerActive = false; + mSecondaryTimerExpireTimestamp = 0; mSecondaryTimerState = ""; updateTimers(); mLastShownNrDueToAdvancedBand = false; @@ -1035,11 +1042,15 @@ public class NetworkTypeController extends StateMachine { if (isUsingPhysicalChannelConfigForRrcDetection()) { mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig(); } - // Check NR advanced in case NR advanced bands were added - if (isNrAdvanced()) { - transitionTo(mNrConnectedAdvancedState); - } else if (isPhysicalLinkActive()) { - transitionWithTimerTo(mNrConnectedState); + if (isPhysicalLinkActive()) { + if (isNrAdvanced()) { + transitionTo(mNrConnectedAdvancedState); + } else { + transitionWithTimerTo(mNrConnectedState); + } + } else { + // Update in case the override network type changed + updateOverrideNetworkType(); } break; case EVENT_PHYSICAL_LINK_STATUS_CHANGED: @@ -1113,11 +1124,10 @@ public class NetworkTypeController extends StateMachine { if (isUsingPhysicalChannelConfigForRrcDetection()) { mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig(); } - // Check NR advanced in case NR advanced bands were added - if (isNrAdvanced()) { - transitionTo(mNrConnectedAdvancedState); - } else if (!isPhysicalLinkActive() && mFeatureFlags.supportNrSaRrcIdle()) { + if (!isPhysicalLinkActive() && mFeatureFlags.supportNrSaRrcIdle()) { transitionWithTimerTo(mNrIdleState); + } else if (isNrAdvanced()) { + transitionTo(mNrConnectedAdvancedState); } break; case EVENT_PHYSICAL_LINK_STATUS_CHANGED: @@ -1203,11 +1213,10 @@ public class NetworkTypeController extends StateMachine { if (isUsingPhysicalChannelConfigForRrcDetection()) { mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig(); } - // Check NR advanced in case NR advanced bands were removed - if (!isNrAdvanced()) { - transitionWithTimerTo(isPhysicalLinkActive() - || !mFeatureFlags.supportNrSaRrcIdle() - ? mNrConnectedState : mNrIdleState); + if (!isPhysicalLinkActive() && mFeatureFlags.supportNrSaRrcIdle()) { + transitionWithTimerTo(mNrIdleState); + } else if (!isNrAdvanced()) { + transitionWithTimerTo(mNrConnectedState); } break; case EVENT_PHYSICAL_LINK_STATUS_CHANGED: @@ -1246,6 +1255,8 @@ public class NetworkTypeController extends StateMachine { private void updatePhysicalChannelConfigs(List<PhysicalChannelConfig> physicalChannelConfigs) { boolean isPccListEmpty = physicalChannelConfigs == null || physicalChannelConfigs.isEmpty(); if (isPccListEmpty && isUsingPhysicalChannelConfigForRrcDetection()) { + // Clear mPrimaryCellChangedWhileIdle to allow later potential one-off PCI change. + // Update link status to be DORMANT, but keep ratcheted bands. log("Physical channel configs updated: not updating PCC fields for empty PCC list " + "indicating RRC idle."); mPrimaryCellChangedWhileIdle = false; @@ -1299,12 +1310,13 @@ public class NetworkTypeController extends StateMachine { } else { if (mFeatureFlags.supportNrSaRrcIdle() && mDoesPccListIndicateIdle && isUsingPhysicalChannelConfigForRrcDetection() - && !mPrimaryCellChangedWhileIdle && isTimerActiveForRrcIdle() + && !mPrimaryCellChangedWhileIdle && !isNrAdvancedForPccFields(nrBandwidths, nrBands)) { - log("Allow primary cell change during RRC idle timer without changing state: " + log("Allow primary cell change once during RRC idle without changing state: " + mLastAnchorNrCellId + " -> " + anchorNrCellId); mPrimaryCellChangedWhileIdle = true; mLastAnchorNrCellId = anchorNrCellId; + reduceSecondaryTimerIfNeeded(); return; } if (mRatchetPccFieldsForSameAnchorNrCell) { @@ -1325,17 +1337,45 @@ public class NetworkTypeController extends StateMachine { } } + /** + * Called when PCI change, specifically during idle state. + */ + private void reduceSecondaryTimerIfNeeded() { + if (!mIsSecondaryTimerActive || mNrAdvancedBandsSecondaryTimer <= 0) return; + // Secondary timer is active, so we must have a valid secondary rule right now. + OverrideTimerRule secondaryRule = mOverrideTimerRules.get(mPrimaryTimerState); + if (secondaryRule != null) { + int secondaryDuration = secondaryRule.getSecondaryTimer(mSecondaryTimerState); + long durationMillis = secondaryDuration * 1000L; + if ((mSecondaryTimerExpireTimestamp - SystemClock.uptimeMillis()) > durationMillis) { + if (DBG) log("Due to PCI change, reduce the secondary timer to " + durationMillis); + removeMessages(EVENT_SECONDARY_TIMER_EXPIRED); + sendMessageDelayed(EVENT_SECONDARY_TIMER_EXPIRED, mSecondaryTimerState, + durationMillis); + } + } else { + loge("!! Secondary timer is active, but found no rule for " + mPrimaryTimerState); + } + } + private void transitionWithTimerTo(IState destState) { String destName = destState.getName(); - if (DBG) log("Transition with primary timer from " + mPreviousState + " to " + destName); - OverrideTimerRule rule = mOverrideTimerRules.get(mPreviousState); - if (!mIsDeviceIdleMode && rule != null && rule.getTimer(destName) > 0) { - int duration = rule.getTimer(destName); - if (DBG) log(duration + "s primary timer started for state: " + mPreviousState); - mPrimaryTimerState = mPreviousState; - mPreviousState = getCurrentState().getName(); - mIsPrimaryTimerActive = true; - sendMessageDelayed(EVENT_PRIMARY_TIMER_EXPIRED, destState, duration * 1000L); + if (mIsPrimaryTimerActive) { + log("Transition without timer from " + getCurrentState().getName() + " to " + destName + + " due to existing " + mPrimaryTimerState + " primary timer."); + } else { + if (DBG) { + log("Transition with primary timer from " + mPreviousState + " to " + destName); + } + OverrideTimerRule rule = mOverrideTimerRules.get(mPreviousState); + if (!mIsDeviceIdleMode && rule != null && rule.getTimer(destName) > 0) { + int duration = rule.getTimer(destName); + if (DBG) log(duration + "s primary timer started for state: " + mPreviousState); + mPrimaryTimerState = mPreviousState; + mPreviousState = getCurrentState().getName(); + mIsPrimaryTimerActive = true; + sendMessageDelayed(EVENT_PRIMARY_TIMER_EXPIRED, destState, duration * 1000L); + } } transitionTo(destState); } @@ -1357,7 +1397,9 @@ public class NetworkTypeController extends StateMachine { mSecondaryTimerState = currentName; mPreviousState = currentName; mIsSecondaryTimerActive = true; - sendMessageDelayed(EVENT_SECONDARY_TIMER_EXPIRED, destState, duration * 1000L); + long durationMillis = duration * 1000L; + mSecondaryTimerExpireTimestamp = SystemClock.uptimeMillis() + durationMillis; + sendMessageDelayed(EVENT_SECONDARY_TIMER_EXPIRED, destState, durationMillis); } mIsPrimaryTimerActive = false; transitionTo(getCurrentState()); @@ -1367,14 +1409,12 @@ public class NetworkTypeController extends StateMachine { int dataRat = getDataNetworkType(); IState transitionState; if (dataRat == TelephonyManager.NETWORK_TYPE_NR || (isLte(dataRat) && isNrConnected())) { - if (isNrAdvanced()) { + if (!isPhysicalLinkActive() && mFeatureFlags.supportNrSaRrcIdle()) { + transitionState = mNrIdleState; + } else if (isNrAdvanced()) { transitionState = mNrConnectedAdvancedState; } else { - if (isPhysicalLinkActive() || !mFeatureFlags.supportNrSaRrcIdle()) { - transitionState = mNrConnectedState; - } else { - transitionState = mNrIdleState; - } + transitionState = mNrConnectedState; } } else if (isLte(dataRat) && isNrNotRestricted()) { if (isPhysicalLinkActive()) { @@ -1403,13 +1443,11 @@ public class NetworkTypeController extends StateMachine { String currentState = getCurrentState().getName(); - if (mIsPrimaryTimerActive && getOverrideNetworkType() == getCurrentOverrideNetworkType() - && getDataNetworkType() - == mDisplayInfoController.getTelephonyDisplayInfo().getNetworkType()) { - // remove primary timer if device goes back to the original icon + if (mIsPrimaryTimerActive && mPrimaryTimerState.equals(currentState)) { + // remove primary timer if device goes back to the original state if (DBG) { - log("Remove primary timer since icon of primary state and current icon equal: " - + mPrimaryTimerState); + log("Remove primary timer since primary timer state (" + + mPrimaryTimerState + ") was reestablished."); } removeMessages(EVENT_PRIMARY_TIMER_EXPIRED); mIsPrimaryTimerActive = false; @@ -1426,6 +1464,7 @@ public class NetworkTypeController extends StateMachine { } removeMessages(EVENT_SECONDARY_TIMER_EXPIRED); mIsSecondaryTimerActive = false; + mSecondaryTimerExpireTimestamp = 0; mSecondaryTimerState = ""; transitionToCurrentState(); return; @@ -1435,10 +1474,11 @@ public class NetworkTypeController extends StateMachine { if (currentState.equals(STATE_CONNECTED_NR_ADVANCED)) { if (DBG) log("Reset timers since state is NR_ADVANCED."); resetAllTimers(); - } else if (currentState.equals(STATE_CONNECTED) + } else if ((currentState.equals(STATE_CONNECTED) + || currentState.equals(STATE_CONNECTED_RRC_IDLE)) && !mPrimaryTimerState.equals(STATE_CONNECTED_NR_ADVANCED) && !mSecondaryTimerState.equals(STATE_CONNECTED_NR_ADVANCED)) { - if (DBG) log("Reset non-NR_ADVANCED timers since state is NR_CONNECTED"); + if (DBG) log("Reset non-NR advanced timers since state is NR connected/idle"); resetAllTimers(); } else { int rat = getDataNetworkType(); @@ -1455,24 +1495,13 @@ public class NetworkTypeController extends StateMachine { removeMessages(EVENT_SECONDARY_TIMER_EXPIRED); mIsPrimaryTimerActive = false; mIsSecondaryTimerActive = false; + mSecondaryTimerExpireTimestamp = 0; mPrimaryTimerState = ""; mSecondaryTimerState = ""; mLastShownNrDueToAdvancedBand = false; } - private boolean isTimerActiveForRrcIdle() { - if (mIsPrimaryTimerActive) { - return mPrimaryTimerState.equals(STATE_CONNECTED_RRC_IDLE) - || mPrimaryTimerState.equals(STATE_NOT_RESTRICTED_RRC_IDLE); - } else if (mIsSecondaryTimerActive) { - return mSecondaryTimerState.equals(STATE_CONNECTED_RRC_IDLE) - || mSecondaryTimerState.equals(STATE_NOT_RESTRICTED_RRC_IDLE); - } else { - return false; - } - } - /** * Private class defining timer rules between states to prevent flickering. These rules are * created in {@link #parseCarrierConfigs()} based on various carrier configs. diff --git a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java index 49e1e387fd..ef96d892c0 100644 --- a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java +++ b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java @@ -560,7 +560,8 @@ public class PhoneConfigurationManager { } public int getNumberOfModemsWithSimultaneousVoiceConnections() { - return getStaticPhoneCapability().getMaxActiveVoiceSubscriptions(); + return maybeOverrideMaxActiveVoiceSubscriptions(mStaticCapability) + .getMaxActiveVoiceSubscriptions(); } public boolean isVirtualDsdaEnabled() { diff --git a/src/java/com/android/internal/telephony/PhoneFactory.java b/src/java/com/android/internal/telephony/PhoneFactory.java index c5bc4283ec..803fb19185 100644 --- a/src/java/com/android/internal/telephony/PhoneFactory.java +++ b/src/java/com/android/internal/telephony/PhoneFactory.java @@ -212,7 +212,7 @@ public class PhoneFactory { Looper.myLooper(), featureFlags); TelephonyComponentFactory.getInstance().inject(MultiSimSettingController.class. - getName()).initMultiSimSettingController(context); + getName()).initMultiSimSettingController(context, featureFlags); if (context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_TELEPHONY_EUICC)) { diff --git a/src/java/com/android/internal/telephony/RIL.java b/src/java/com/android/internal/telephony/RIL.java index 5177adb3b9..8b3be1ea84 100644 --- a/src/java/com/android/internal/telephony/RIL.java +++ b/src/java/com/android/internal/telephony/RIL.java @@ -31,6 +31,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; +import android.content.pm.PackageManager; import android.hardware.radio.V1_0.IRadio; import android.hardware.radio.V1_0.RadioError; import android.hardware.radio.V1_0.RadioIndicationType; @@ -271,6 +272,13 @@ public class RIL extends BaseCommands implements CommandsInterface { static final String[] HIDL_SERVICE_NAME = {"slot1", "slot2", "slot3"}; + private static final Map<String, Integer> FEATURES_TO_SERVICES = Map.ofEntries( + Map.entry(PackageManager.FEATURE_TELEPHONY_CALLING, HAL_SERVICE_VOICE), + Map.entry(PackageManager.FEATURE_TELEPHONY_DATA, HAL_SERVICE_DATA), + Map.entry(PackageManager.FEATURE_TELEPHONY_MESSAGING, HAL_SERVICE_MESSAGING), + Map.entry(PackageManager.FEATURE_TELEPHONY_IMS, HAL_SERVICE_IMS) + ); + public static List<TelephonyHistogram> getTelephonyRILTimingHistograms() { List<TelephonyHistogram> list; synchronized (sRilTimeHistograms) { @@ -621,6 +629,7 @@ public class RIL extends BaseCommands implements CommandsInterface { if (mDisabledRadioServices.get(HAL_SERVICE_RADIO).contains(mPhoneId)) { riljLoge("getRadioProxy: mRadioProxy for " + HIDL_SERVICE_NAME[mPhoneId] + " is disabled"); + return null; } else { try { mRadioProxy = android.hardware.radio.V1_6.IRadio.getService( @@ -662,6 +671,7 @@ public class RIL extends BaseCommands implements CommandsInterface { mDisabledRadioServices.get(HAL_SERVICE_RADIO).add(mPhoneId); riljLoge("getRadioProxy: set mRadioProxy for " + HIDL_SERVICE_NAME[mPhoneId] + " as disabled"); + return null; } } } catch (RemoteException e) { @@ -718,6 +728,9 @@ public class RIL extends BaseCommands implements CommandsInterface { public synchronized RadioServiceProxy getRadioServiceProxy(int service) { if (!SubscriptionManager.isValidPhoneId(mPhoneId)) return mServiceProxies.get(service); if ((service >= HAL_SERVICE_IMS) && !isRadioServiceSupported(service)) { + riljLogw("getRadioServiceProxy: " + serviceToString(service) + " for " + + HIDL_SERVICE_NAME[mPhoneId] + " is not supported\n" + + android.util.Log.getStackTraceString(new RuntimeException())); return mServiceProxies.get(service); } if (!mIsCellularSupported) { @@ -733,7 +746,9 @@ public class RIL extends BaseCommands implements CommandsInterface { try { if (mMockModem == null && mDisabledRadioServices.get(service).contains(mPhoneId)) { riljLoge("getRadioServiceProxy: " + serviceToString(service) + " for " - + HIDL_SERVICE_NAME[mPhoneId] + " is disabled"); + + HIDL_SERVICE_NAME[mPhoneId] + " is disabled\n" + + android.util.Log.getStackTraceString(new RuntimeException())); + return null; } else { IBinder binder; switch (service) { @@ -944,7 +959,8 @@ public class RIL extends BaseCommands implements CommandsInterface { mDisabledRadioServices.get(service).add(mPhoneId); mHalVersion.put(service, RADIO_HAL_VERSION_UNKNOWN); riljLoge("getRadioServiceProxy: set " + serviceToString(service) + " for " - + HIDL_SERVICE_NAME[mPhoneId] + " as disabled"); + + HIDL_SERVICE_NAME[mPhoneId] + " as disabled\n" + + android.util.Log.getStackTraceString(new RuntimeException())); } } } catch (RemoteException e) { @@ -1094,9 +1110,16 @@ public class RIL extends BaseCommands implements CommandsInterface { tdc.registerRIL(this); } + validateFeatureFlags(); + // Set radio callback; needed to set RadioIndication callback (should be done after // wakelock stuff is initialized above as callbacks are received on separate binder threads) for (int service = MIN_SERVICE_IDX; service <= MAX_SERVICE_IDX; service++) { + if (!isRadioServiceSupported(service)) { + riljLog("Not initializing " + serviceToString(service) + " (not supported)"); + continue; + } + if (service == HAL_SERVICE_RADIO) { getRadioProxy(); } else { @@ -1161,6 +1184,26 @@ public class RIL extends BaseCommands implements CommandsInterface { return false; } + private void validateFeatureFlags() { + PackageManager pm = mContext.getPackageManager(); + for (var entry : FEATURES_TO_SERVICES.entrySet()) { + String feature = entry.getKey(); + int service = entry.getValue(); + + boolean hasFeature = pm.hasSystemFeature(feature); + boolean hasService = isRadioServiceSupported(service); + + if (hasFeature && !hasService) { + riljLoge("Feature " + feature + " is declared, but service " + + serviceToString(service) + " is missing"); + } + if (!hasFeature && hasService) { + riljLoge("Service " + serviceToString(service) + " is available, but feature " + + feature + " is not declared"); + } + } + } + private boolean isRadioBugDetectionEnabled() { return Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.ENABLE_RADIO_BUG_DETECTION, 1) != 0; diff --git a/src/java/com/android/internal/telephony/RadioInterfaceCapabilityController.java b/src/java/com/android/internal/telephony/RadioInterfaceCapabilityController.java index bab4d12185..4d9196e469 100644 --- a/src/java/com/android/internal/telephony/RadioInterfaceCapabilityController.java +++ b/src/java/com/android/internal/telephony/RadioInterfaceCapabilityController.java @@ -86,6 +86,13 @@ public class RadioInterfaceCapabilityController extends Handler { register(); } + private void requestCapabilities() { + if (mRadioInterfaceCapabilities != null) return; + + mRadioConfig.getHalDeviceCapabilities(obtainMessage( + EVENT_GET_HAL_DEVICE_CAPABILITIES_DONE)); + } + /** * Gets the radio interface capabilities for the device */ @@ -95,8 +102,7 @@ public class RadioInterfaceCapabilityController extends Handler { // Only incur cost of synchronization block if mRadioInterfaceCapabilities isn't null synchronized (mLockRadioInterfaceCapabilities) { if (mRadioInterfaceCapabilities == null) { - mRadioConfig.getHalDeviceCapabilities( - obtainMessage(EVENT_GET_HAL_DEVICE_CAPABILITIES_DONE)); + requestCapabilities(); try { if (Looper.myLooper() != getLooper()) { mLockRadioInterfaceCapabilities.wait(2000); @@ -158,7 +164,7 @@ public class RadioInterfaceCapabilityController extends Handler { switch (msg.what) { case Phone.EVENT_RADIO_AVAILABLE: case Phone.EVENT_RADIO_ON: - getCapabilities(); + requestCapabilities(); break; case EVENT_GET_HAL_DEVICE_CAPABILITIES_DONE: setupCapabilities((AsyncResult) msg.obj); diff --git a/src/java/com/android/internal/telephony/SMSDispatcher.java b/src/java/com/android/internal/telephony/SMSDispatcher.java index 04f5c083ba..498535bc4e 100644 --- a/src/java/com/android/internal/telephony/SMSDispatcher.java +++ b/src/java/com/android/internal/telephony/SMSDispatcher.java @@ -1013,10 +1013,12 @@ public abstract class SMSDispatcher extends Handler { * Notifies the {@link SmsDispatchersController} that sending MO SMS is failed. * * @param tracker holds the SMS message to be sent + * @param isOverIms a flag specifying whether SMS is sent via IMS or not */ - protected void notifySmsSentFailedToEmergencyStateTracker(SmsTracker tracker) { + protected void notifySmsSentFailedToEmergencyStateTracker(SmsTracker tracker, + boolean isOverIms) { mSmsDispatchersController.notifySmsSentFailedToEmergencyStateTracker( - tracker.mDestAddress, tracker.mMessageId); + tracker.mDestAddress, tracker.mMessageId, isOverIms); } /** @@ -1052,7 +1054,7 @@ public abstract class SMSDispatcher extends Handler { tracker.onSent(mContext); mPhone.notifySmsSent(tracker.mDestAddress); mSmsDispatchersController.notifySmsSentToEmergencyStateTracker( - tracker.mDestAddress, tracker.mMessageId); + tracker.mDestAddress, tracker.mMessageId, false); mPhone.getSmsStats().onOutgoingSms( tracker.mImsRetry > 0 /* isOverIms */, @@ -1103,7 +1105,7 @@ public abstract class SMSDispatcher extends Handler { // if sms over IMS is not supported on data and voice is not available... if (!isIms() && ss != ServiceState.STATE_IN_SERVICE) { tracker.onFailed(mContext, getNotInServiceError(ss), NO_ERROR_CODE); - notifySmsSentFailedToEmergencyStateTracker(tracker); + notifySmsSentFailedToEmergencyStateTracker(tracker, false); mPhone.getSmsStats().onOutgoingSms( tracker.mImsRetry > 0 /* isOverIms */, SmsConstants.FORMAT_3GPP2.equals(getFormat()), @@ -1164,7 +1166,7 @@ public abstract class SMSDispatcher extends Handler { } else { int errorCode = (smsResponse != null) ? smsResponse.mErrorCode : NO_ERROR_CODE; tracker.onFailed(mContext, error, errorCode); - notifySmsSentFailedToEmergencyStateTracker(tracker); + notifySmsSentFailedToEmergencyStateTracker(tracker, false); mPhone.getSmsStats().onOutgoingSms( tracker.mImsRetry > 0 /* isOverIms */, SmsConstants.FORMAT_3GPP2.equals(getFormat()), @@ -2389,7 +2391,7 @@ public abstract class SMSDispatcher extends Handler { int errorCode) { for (SmsTracker tracker : trackers) { tracker.onFailed(mContext, error, errorCode); - notifySmsSentFailedToEmergencyStateTracker(tracker); + notifySmsSentFailedToEmergencyStateTracker(tracker, false); } if (trackers.length > 0) { // This error occurs before the SMS is sent. Make an assumption if it would have diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java index 88d541c38f..ba6479ed03 100644 --- a/src/java/com/android/internal/telephony/ServiceStateTracker.java +++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java @@ -739,7 +739,7 @@ public class ServiceStateTracker extends Handler { mAccessNetworksManagerCallback = new AccessNetworksManagerCallback(this::post) { @Override - public void onPreferredTransportChanged(int networkCapability) { + public void onPreferredTransportChanged(int networkCapability, boolean forceReconnect) { // Check if preferred on IWLAN was changed in ServiceState. boolean isIwlanPreferred = mAccessNetworksManager.isAnyApnOnIwlan(); if (mSS.isIwlanPreferred() != isIwlanPreferred) { diff --git a/src/java/com/android/internal/telephony/SmsDispatchersController.java b/src/java/com/android/internal/telephony/SmsDispatchersController.java index 8795840e9a..cc287f8055 100644 --- a/src/java/com/android/internal/telephony/SmsDispatchersController.java +++ b/src/java/com/android/internal/telephony/SmsDispatchersController.java @@ -109,6 +109,9 @@ public class SmsDispatchersController extends Handler { /** Called when AP domain selection is abnormally terminated. */ private static final int EVENT_DOMAIN_SELECTION_TERMINATED_ABNORMALLY = 20; + /** Called when MT SMS is received via IMS. */ + private static final int EVENT_SMS_RECEIVED_VIA_IMS = 21; + /** Delete any partial message segments after being IN_SERVICE for 1 day. */ private static final long PARTIAL_SEGMENT_WAIT_DURATION = (long) (60 * 60 * 1000) * 24; /** Constant for invalid time */ @@ -487,8 +490,10 @@ public class SmsDispatchersController extends Handler { String destAddr = (String) args.arg1; Long messageId = (Long) args.arg2; Boolean success = (Boolean) args.arg3; + Boolean isOverIms = (Boolean) args.arg4; try { - handleSmsSentCompletedUsingDomainSelection(destAddr, messageId, success); + handleSmsSentCompletedUsingDomainSelection( + destAddr, messageId, success, isOverIms); } finally { args.recycle(); } @@ -499,6 +504,10 @@ public class SmsDispatchersController extends Handler { (DomainSelectionConnectionHolder) msg.obj); break; } + case EVENT_SMS_RECEIVED_VIA_IMS: { + handleSmsReceivedViaIms((String) msg.obj); + break; + } default: if (isCdmaMo()) { mCdmaDispatcher.handleMessage(msg); @@ -808,7 +817,7 @@ public class SmsDispatchersController extends Handler { Rlog.e(TAG, "sendRetrySms failed to re-encode per missing fields!"); tracker.onFailed(mContext, SmsManager.RESULT_SMS_SEND_RETRY_FAILED, NO_ERROR_CODE); notifySmsSentFailedToEmergencyStateTracker( - tracker.mDestAddress, tracker.mMessageId); + tracker.mDestAddress, tracker.mMessageId, !retryUsingImsService); return; } String scAddr = (String) map.get("scAddr"); @@ -817,7 +826,7 @@ public class SmsDispatchersController extends Handler { Rlog.e(TAG, "sendRetrySms failed due to null destAddr"); tracker.onFailed(mContext, SmsManager.RESULT_SMS_SEND_RETRY_FAILED, NO_ERROR_CODE); notifySmsSentFailedToEmergencyStateTracker( - tracker.mDestAddress, tracker.mMessageId); + tracker.mDestAddress, tracker.mMessageId, !retryUsingImsService); return; } @@ -859,7 +868,7 @@ public class SmsDispatchersController extends Handler { + "destPort: %s", scAddr, map.get("destPort"))); tracker.onFailed(mContext, SmsManager.RESULT_SMS_SEND_RETRY_FAILED, NO_ERROR_CODE); notifySmsSentFailedToEmergencyStateTracker( - tracker.mDestAddress, tracker.mMessageId); + tracker.mDestAddress, tracker.mMessageId, !retryUsingImsService); return; } // replace old smsc and pdu with newly encoded ones @@ -1147,13 +1156,16 @@ public class SmsDispatchersController extends Handler { * @param destAddr The destination address for SMS. * @param messageId The message id for SMS. * @param success A flag specifying whether MO SMS is successfully sent or not. + * @param isOverIms A flag specifying whether MO SMS is sent over IMS or not. */ private void handleSmsSentCompletedUsingDomainSelection(@NonNull String destAddr, - long messageId, boolean success) { + long messageId, boolean success, boolean isOverIms) { if (mEmergencyStateTracker != null) { TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); if (tm.isEmergencyNumber(destAddr)) { - mEmergencyStateTracker.endSms(String.valueOf(messageId), success); + mEmergencyStateTracker.endSms(String.valueOf(messageId), success, + isOverIms ? NetworkRegistrationInfo.DOMAIN_PS + : NetworkRegistrationInfo.DOMAIN_CS); } } } @@ -1161,13 +1173,15 @@ public class SmsDispatchersController extends Handler { /** * Called when MO SMS is successfully sent. */ - protected void notifySmsSentToEmergencyStateTracker(@NonNull String destAddr, long messageId) { + protected void notifySmsSentToEmergencyStateTracker(@NonNull String destAddr, long messageId, + boolean isOverIms) { if (isSmsDomainSelectionEnabled()) { // Run on main thread for interworking with EmergencyStateTracker. SomeArgs args = SomeArgs.obtain(); args.arg1 = destAddr; args.arg2 = Long.valueOf(messageId); args.arg3 = Boolean.TRUE; + args.arg4 = Boolean.valueOf(isOverIms); sendMessage(obtainMessage(EVENT_SMS_SENT_COMPLETED_USING_DOMAIN_SELECTION, args)); } } @@ -1176,17 +1190,42 @@ public class SmsDispatchersController extends Handler { * Called when sending MO SMS is failed. */ protected void notifySmsSentFailedToEmergencyStateTracker(@NonNull String destAddr, - long messageId) { + long messageId, boolean isOverIms) { if (isSmsDomainSelectionEnabled()) { // Run on main thread for interworking with EmergencyStateTracker. SomeArgs args = SomeArgs.obtain(); args.arg1 = destAddr; args.arg2 = Long.valueOf(messageId); args.arg3 = Boolean.FALSE; + args.arg4 = Boolean.valueOf(isOverIms); sendMessage(obtainMessage(EVENT_SMS_SENT_COMPLETED_USING_DOMAIN_SELECTION, args)); } } + /** + * Called when MT SMS is received via IMS. + * + * @param origAddr The originating address of MT SMS. + */ + private void handleSmsReceivedViaIms(@Nullable String origAddr) { + if (mEmergencyStateTracker != null) { + TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); + if (origAddr != null && tm.isEmergencyNumber(origAddr)) { + mEmergencyStateTracker.onEmergencySmsReceived(); + } + } + } + + /** + * Called when MT SMS is received via IMS. + */ + protected void notifySmsReceivedViaImsToEmergencyStateTracker(@Nullable String origAddr) { + if (isSmsDomainSelectionEnabled()) { + // Run on main thread for interworking with EmergencyStateTracker. + sendMessage(obtainMessage(EVENT_SMS_RECEIVED_VIA_IMS, origAddr)); + } + } + private boolean isTestEmergencyNumber(String number) { try { TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); diff --git a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java index f5aa0746c5..5da4b12b5a 100644 --- a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java +++ b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java @@ -305,8 +305,9 @@ public class TelephonyComponentFactory { /** * Create a new EmergencyNumberTracker. */ - public EmergencyNumberTracker makeEmergencyNumberTracker(Phone phone, CommandsInterface ci) { - return new EmergencyNumberTracker(phone, ci); + public EmergencyNumberTracker makeEmergencyNumberTracker(Phone phone, CommandsInterface ci, + @NonNull FeatureFlags featureFlags) { + return new EmergencyNumberTracker(phone, ci, featureFlags); } private static final boolean USE_NEW_NITZ_STATE_MACHINE = true; @@ -507,8 +508,9 @@ public class TelephonyComponentFactory { * @param c The context. * @return The multi sim settings controller instance. */ - public MultiSimSettingController initMultiSimSettingController(Context c) { - return MultiSimSettingController.init(c); + public MultiSimSettingController initMultiSimSettingController(Context c, + @NonNull FeatureFlags featureFlags) { + return MultiSimSettingController.init(c, featureFlags); } /** @@ -571,9 +573,11 @@ public class TelephonyComponentFactory { * @return The data settings manager instance. */ public @NonNull DataSettingsManager makeDataSettingsManager(@NonNull Phone phone, - @NonNull DataNetworkController dataNetworkController, @NonNull Looper looper, + @NonNull DataNetworkController dataNetworkController, + @NonNull FeatureFlags featureFlags, @NonNull Looper looper, @NonNull DataSettingsManager.DataSettingsManagerCallback callback) { - return new DataSettingsManager(phone, dataNetworkController, looper, callback); + return new DataSettingsManager(phone, dataNetworkController, featureFlags, looper, + callback); } /** Create CellularNetworkSecuritySafetySource. */ @@ -589,7 +593,8 @@ public class TelephonyComponentFactory { } /** Create NullCipherNotifier. */ - public NullCipherNotifier makeNullCipherNotifier() { - return NullCipherNotifier.getInstance(); + public NullCipherNotifier makeNullCipherNotifier( + CellularNetworkSecuritySafetySource safetySource) { + return NullCipherNotifier.getInstance(safetySource); } } diff --git a/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java index 2119003d14..f40b6d6c0a 100644 --- a/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java +++ b/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java @@ -129,7 +129,7 @@ public class CdmaSMSDispatcher extends SMSDispatcher { // if sms over IMS is not supported on data and voice is not available... if (!isIms() && ss != ServiceState.STATE_IN_SERVICE) { tracker.onFailed(mContext, getNotInServiceError(ss), NO_ERROR_CODE); - notifySmsSentFailedToEmergencyStateTracker(tracker); + notifySmsSentFailedToEmergencyStateTracker(tracker, false); return; } diff --git a/src/java/com/android/internal/telephony/configupdate/ConfigParser.java b/src/java/com/android/internal/telephony/configupdate/ConfigParser.java new file mode 100644 index 0000000000..5c3ac866df --- /dev/null +++ b/src/java/com/android/internal/telephony/configupdate/ConfigParser.java @@ -0,0 +1,100 @@ +/* + * 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.configupdate; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +public abstract class ConfigParser<T> { + + public static final int VERSION_UNKNOWN = -1; + + protected int mVersion = VERSION_UNKNOWN; + + protected T mConfig; + + /** + * Constructs a parser from the raw data + * + * @param data the config data + */ + public ConfigParser(@Nullable byte[] data) { + parseData(data); + } + + /** + * Constructs a parser from the input stream + * + * @param input the input stream of the config + * @return the instance of the ConfigParser + */ + public ConfigParser(@NonNull InputStream input) throws IOException { + parseData(input.readAllBytes()); + } + + /** + * Constructs a parser from the input stream + * + * @param file the input file of the config + * @return the instance of the ConfigParser + */ + public ConfigParser(@NonNull File file) throws IOException { + try (FileInputStream fis = new FileInputStream(file)) { + parseData(fis.readAllBytes()); + } + } + + /** + * Get the version of the config + * + * @return the version of the config if it is defined, or VERSION_UNKNOWN + */ + public int getVersion() { + return mVersion; + } + + /** + * Get the config + * + * @return the config + */ + public @Nullable T getConfig() { + return mConfig; + } + + /** + * Get the sub config raw data by id + * + * @param id the identifier of the sub config + * @return the raw data of the sub config + */ + public @Nullable byte[] getData(String id) { + return null; + } + + /** + * Parse the config data + * + * @param data the config data + */ + protected abstract void parseData(@Nullable byte[] data); +} diff --git a/src/java/com/android/internal/telephony/configupdate/ConfigProviderAdaptor.java b/src/java/com/android/internal/telephony/configupdate/ConfigProviderAdaptor.java new file mode 100644 index 0000000000..1344534daf --- /dev/null +++ b/src/java/com/android/internal/telephony/configupdate/ConfigProviderAdaptor.java @@ -0,0 +1,54 @@ +/* + * 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.configupdate; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.util.concurrent.Executor; + +public interface ConfigProviderAdaptor { + + String DOMAIN_SATELLITE = "satellite"; + + /** + * Get the config from the provider + */ + @Nullable ConfigParser getConfigParser(String domain); + + class Callback { + /** + * The callback when the config is changed + * @param config the config is changed + */ + public void onChanged(@Nullable ConfigParser config) {} + } + + /** + * Register the callback to monitor the config change + * @param executor The executor to execute the callback. + * @param callback the callback to monitor the config change + */ + void registerCallback(@NonNull Executor executor, @NonNull Callback callback); + + /** + * Unregister the callback + * @param callback the callback to be unregistered + */ + void unregisterCallback(@NonNull Callback callback); +} diff --git a/src/java/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiver.java b/src/java/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiver.java new file mode 100644 index 0000000000..0db7844c8d --- /dev/null +++ b/src/java/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiver.java @@ -0,0 +1,200 @@ +/* + * 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.configupdate; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.Intent; +import android.util.Log; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.satellite.SatelliteConfigParser; +import com.android.server.updates.ConfigUpdateInstallReceiver; + +import libcore.io.IoUtils; + +import java.io.File; +import java.io.IOException; +import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; + +public class TelephonyConfigUpdateInstallReceiver extends ConfigUpdateInstallReceiver implements + ConfigProviderAdaptor { + + private static final String TAG = "TelephonyConfigUpdateInstallReceiver"; + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + protected static final String UPDATE_DIR = "/data/misc/telephonyconfig"; + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + protected static final String UPDATE_CONTENT_PATH = "telephony_config.pb"; + protected static final String UPDATE_METADATA_PATH = "metadata/"; + public static final String VERSION = "version"; + + private ConcurrentHashMap<Executor, Callback> mCallbackHashMap = new ConcurrentHashMap<>(); + @NonNull + private final Object mConfigParserLock = new Object(); + @GuardedBy("mConfigParserLock") + private ConfigParser mConfigParser; + + + public static TelephonyConfigUpdateInstallReceiver sReceiverAdaptorInstance = + new TelephonyConfigUpdateInstallReceiver(); + + /** + * @return The singleton instance of TelephonyConfigUpdateInstallReceiver + */ + @NonNull + public static TelephonyConfigUpdateInstallReceiver getInstance() { + return sReceiverAdaptorInstance; + } + + public TelephonyConfigUpdateInstallReceiver() { + super(UPDATE_DIR, UPDATE_CONTENT_PATH, UPDATE_METADATA_PATH, VERSION); + } + + /** + * @return byte array type of config data protobuffer file + */ + @Nullable + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public byte[] getCurrentContent() { + try { + return IoUtils.readFileAsByteArray(updateContent.getCanonicalPath()); + } catch (IOException e) { + Slog.i(TAG, "Failed to read current content, assuming first update!"); + return null; + } + } + + @Override + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) + public void postInstall(Context context, Intent intent) { + Log.d(TAG, "Telephony config is updated in file partition"); + ConfigParser updatedConfigParser = getNewConfigParser(DOMAIN_SATELLITE, + getCurrentContent()); + + if (updatedConfigParser == null) { + Log.d(TAG, "updatedConfigParser is null"); + return; + } + + boolean isParserChanged = false; + + synchronized (getInstance().mConfigParserLock) { + if (getInstance().mConfigParser == null) { + getInstance().mConfigParser = updatedConfigParser; + isParserChanged = true; + } else { + int updatedVersion = updatedConfigParser.mVersion; + int previousVersion = getInstance().mConfigParser.mVersion; + Log.d(TAG, "previous version is " + previousVersion + " | updated version is " + + updatedVersion); + if (updatedVersion > previousVersion) { + getInstance().mConfigParser = updatedConfigParser; + isParserChanged = true; + } + } + } + + if (isParserChanged) { + if (getInstance().mCallbackHashMap.keySet().isEmpty()) { + Log.d(TAG, "mCallbackHashMap.keySet().isEmpty"); + return; + } + Iterator<Executor> iterator = + getInstance().mCallbackHashMap.keySet().iterator(); + while (iterator.hasNext()) { + Executor executor = iterator.next(); + getInstance().mCallbackHashMap.get(executor).onChanged( + updatedConfigParser); + } + } + } + + @Nullable + @Override + public ConfigParser getConfigParser(String domain) { + Log.d(TAG, "getConfigParser"); + synchronized (getInstance().mConfigParserLock) { + if (getInstance().mConfigParser == null) { + Log.d(TAG, "CreateNewConfigParser with domain " + domain); + getInstance().mConfigParser = getNewConfigParser(domain, getCurrentContent()); + } + return getInstance().mConfigParser; + } + } + + @Override + public void registerCallback(@NonNull Executor executor, @NonNull Callback callback) { + mCallbackHashMap.put(executor, callback); + } + + @Override + public void unregisterCallback(@NonNull Callback callback) { + Iterator<Executor> iterator = mCallbackHashMap.keySet().iterator(); + while (iterator.hasNext()) { + Executor executor = iterator.next(); + if (mCallbackHashMap.get(executor) == callback) { + mCallbackHashMap.remove(executor); + break; + } + } + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public File getUpdateDir() { + return getInstance().updateDir; + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public File getUpdateContent() { + return getInstance().updateContent; + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public ConcurrentHashMap<Executor, Callback> getCallbackMap() { + return getInstance().mCallbackHashMap; + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public void setCallbackMap(ConcurrentHashMap<Executor, Callback> map) { + getInstance().mCallbackHashMap = map; + } + + /** + * @param data byte array type of config data + * @return when data is null, return null otherwise return ConfigParser + */ + @Nullable + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public ConfigParser getNewConfigParser(String domain, @Nullable byte[] data) { + if (data == null) { + Log.d(TAG, "content data is null"); + return null; + } + switch (domain) { + case DOMAIN_SATELLITE: + return new SatelliteConfigParser(data); + default: + Log.e(TAG, "DOMAIN should be specified"); + return null; + } + } +} diff --git a/src/java/com/android/internal/telephony/data/AccessNetworksManager.java b/src/java/com/android/internal/telephony/data/AccessNetworksManager.java index a657ecd8cf..3d3fbe959c 100644 --- a/src/java/com/android/internal/telephony/data/AccessNetworksManager.java +++ b/src/java/com/android/internal/telephony/data/AccessNetworksManager.java @@ -234,6 +234,11 @@ public class AccessNetworksManager extends Handler { .mapToObj(AccessNetworkType::toString).collect(Collectors.joining(",")) + "]"); + handleQualifiedNetworksChanged(apnTypes, qualifiedNetworkTypes, false); + } + + private void handleQualifiedNetworksChanged( + int apnTypes, int[] qualifiedNetworkTypes, boolean forceReconnect) { if (Arrays.stream(qualifiedNetworkTypes).anyMatch(accessNetwork -> !DataUtils.isValidAccessNetwork(accessNetwork))) { loge("Invalid access networks " + Arrays.toString(qualifiedNetworkTypes)); @@ -274,8 +279,9 @@ public class AccessNetworksManager extends Handler { AccessNetworkConstants.TRANSPORT_TYPE_WWAN); mAccessNetworksManagerCallbacks.forEach(callback -> callback.invokeFromExecutor(() -> - callback.onPreferredTransportChanged(DataUtils - .apnTypeToNetworkCapability(apnType)))); + callback.onPreferredTransportChanged( + DataUtils.apnTypeToNetworkCapability(apnType), + forceReconnect))); } } else { mAvailableNetworks.put(apnType, qualifiedNetworkTypes); @@ -297,7 +303,7 @@ public class AccessNetworksManager extends Handler { } if (!qualifiedNetworksList.isEmpty()) { - setPreferredTransports(qualifiedNetworksList); + setPreferredTransports(qualifiedNetworksList, forceReconnect); mQualifiedNetworksChangedRegistrants.notifyResult(qualifiedNetworksList); } } @@ -338,6 +344,17 @@ public class AccessNetworksManager extends Handler { } }); } + + @Override + public void onReconnectQualifedNetworkType(int apnTypes, int qualifiedNetworkType) { + if (mFeatureFlags.reconnectQualifiedNetwork()) { + log("onReconnectQualifedNetworkType: apnTypes = [" + + ApnSetting.getApnTypesStringFromBitmask(apnTypes) + + "], networks = [" + AccessNetworkType.toString(qualifiedNetworkType) + + "]"); + handleQualifiedNetworksChanged(apnTypes, new int[]{qualifiedNetworkType}, true); + } + } } private void onEmergencyDataNetworkPreferredTransportChanged( @@ -371,8 +388,10 @@ public class AccessNetworksManager extends Handler { * Called when preferred transport changed. * * @param networkCapability The network capability. + * @param forceReconnect whether enforce reconnection to the preferred transport type. */ - public abstract void onPreferredTransportChanged(@NetCapability int networkCapability); + public abstract void onPreferredTransportChanged( + @NetCapability int networkCapability, boolean forceReconnect); } /** @@ -610,7 +629,8 @@ public class AccessNetworksManager extends Handler { : AccessNetworkConstants.TRANSPORT_TYPE_WWAN; } - private void setPreferredTransports(@NonNull List<QualifiedNetworks> networksList) { + private void setPreferredTransports( + @NonNull List<QualifiedNetworks> networksList, boolean forceReconnect) { for (QualifiedNetworks networks : networksList) { if (networks.qualifiedNetworks.length > 0) { int transport = getTransportFromAccessNetwork(networks.qualifiedNetworks[0]); @@ -618,11 +638,13 @@ public class AccessNetworksManager extends Handler { mPreferredTransports.put(networks.apnType, transport); mAccessNetworksManagerCallbacks.forEach(callback -> callback.invokeFromExecutor(() -> - callback.onPreferredTransportChanged(DataUtils - .apnTypeToNetworkCapability(networks.apnType)))); + callback.onPreferredTransportChanged( + DataUtils.apnTypeToNetworkCapability(networks.apnType), + forceReconnect))); logl("setPreferredTransports: apnType=" + ApnSetting.getApnTypeString(networks.apnType) + ", transport=" - + AccessNetworkConstants.transportTypeToString(transport)); + + AccessNetworkConstants.transportTypeToString(transport) + + (forceReconnect ? ", forceReconnect:true" : "")); } } } @@ -643,7 +665,7 @@ public class AccessNetworksManager extends Handler { * Get the preferred transport by network capability. * * @param networkCapability The network capability. (Note that only APN-type capabilities are - * supported. + * supported.) * @return The preferred transport. */ public @TransportType int getPreferredTransportByNetworkCapability( @@ -662,7 +684,7 @@ public class AccessNetworksManager extends Handler { * @return {@code true} if there is any APN is on IWLAN, otherwise {@code false}. */ public boolean isAnyApnOnIwlan() { - for (int apnType : AccessNetworksManager.SUPPORTED_APN_TYPES) { + for (int apnType : SUPPORTED_APN_TYPES) { if (getPreferredTransport(apnType) == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) { return true; } diff --git a/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java b/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java index 02c459a2b2..343bb0b2ed 100644 --- a/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java +++ b/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java @@ -188,6 +188,12 @@ public class AutoDataSwitchController extends Handler { * even if ping test fails. */ private boolean mRequirePingTestBeforeSwitch = true; + /** + * TODO: remove after V. + * To indicate whether allow using roaming nDDS if user enabled its roaming when the DDS is not + * usable(OOS or disabled roaming) + */ + private boolean mAllowNddsRoamning = true; /** The count of consecutive auto switch validation failure **/ private int mAutoSwitchValidationFailedCount = 0; /** @@ -444,6 +450,7 @@ public class AutoDataSwitchController extends Handler { DataConfigManager dataConfig = phone.getDataNetworkController().getDataConfigManager(); mScoreTolerance = dataConfig.getAutoDataSwitchScoreTolerance(); mRequirePingTestBeforeSwitch = dataConfig.isPingTestBeforeAutoDataSwitchRequired(); + mAllowNddsRoamning = dataConfig.doesAutoDataSwitchAllowRoaming(); mAutoDataSwitchAvailabilityStabilityTimeThreshold = dataConfig.getAutoDataSwitchAvailabilityStabilityTimeThreshold(); mAutoDataSwitchPerformanceStabilityTimeThreshold = @@ -694,7 +701,7 @@ public class AutoDataSwitchController extends Handler { boolean isForPerformance = false; boolean needValidation = true; - if (sFeatureFlags.autoSwitchAllowRoaming()) { + if (isNddsRoamingEnabled()) { if (mDefaultNetworkIsOnNonCellular) { debugMessage.append(", back to default as default network") .append(" is active on nonCellular transport"); @@ -820,7 +827,7 @@ public class AutoDataSwitchController extends Handler { return invalidResult; } - if (sFeatureFlags.autoSwitchAllowRoaming()) { + if (isNddsRoamingEnabled()) { // check whether primary and secondary signal status are worth switching if (!isRatSignalStrengthBasedSwitchEnabled() && isHomeService(mPhonesSignalStatus[defaultPhoneId].mDataRegState)) { @@ -842,7 +849,7 @@ public class AutoDataSwitchController extends Handler { Phone secondaryDataPhone = null; PhoneSignalStatus candidatePhoneStatus = mPhonesSignalStatus[phoneId]; - if (sFeatureFlags.autoSwitchAllowRoaming()) { + if (isNddsRoamingEnabled()) { PhoneSignalStatus.UsableState currentUsableState = mPhonesSignalStatus[defaultPhoneId].getUsableState(); PhoneSignalStatus.UsableState candidateUsableState = @@ -919,6 +926,13 @@ public class AutoDataSwitchController extends Handler { } /** + * @return {@code true} If the feature of switching to roaming non DDS is enabled. + */ + private boolean isNddsRoamingEnabled() { + return sFeatureFlags.autoDataSwitchAllowRoaming() && mAllowNddsRoamning; + } + + /** * Called when the current environment suits auto data switch. * Start pre-switch validation if the current environment suits auto data switch for * {@link #mAutoDataSwitchAvailabilityStabilityTimeThreshold} MS. diff --git a/src/java/com/android/internal/telephony/data/DataConfigManager.java b/src/java/com/android/internal/telephony/data/DataConfigManager.java index 90743f85d8..0e06dad972 100644 --- a/src/java/com/android/internal/telephony/data/DataConfigManager.java +++ b/src/java/com/android/internal/telephony/data/DataConfigManager.java @@ -1051,6 +1051,15 @@ public class DataConfigManager extends Handler { } /** + * TODO: remove after V. + * @return To indicate whether allow using roaming nDDS if user enabled its roaming when the DDS + * is not usable(OOS or disabled roaming) + */ + public boolean doesAutoDataSwitchAllowRoaming() { + return mResources.getBoolean(com.android.internal.R.bool.auto_data_switch_allow_roaming); + } + + /** * @return The maximum number of retries when a validation for switching failed. */ public int getAutoDataSwitchValidationMaxRetry() { diff --git a/src/java/com/android/internal/telephony/data/DataNetwork.java b/src/java/com/android/internal/telephony/data/DataNetwork.java index c1e61c6f08..0dbbc5c024 100644 --- a/src/java/com/android/internal/telephony/data/DataNetwork.java +++ b/src/java/com/android/internal/telephony/data/DataNetwork.java @@ -1180,12 +1180,14 @@ public class DataNetwork extends StateMachine { mPhone.getServiceStateTracker().registerForCssIndicatorChanged( getHandler(), EVENT_CSS_INDICATOR_CHANGED, null); - mPhone.getCallTracker().registerForVoiceCallStarted( - getHandler(), EVENT_VOICE_CALL_STARTED, null); - mPhone.getCallTracker().registerForVoiceCallEnded( - getHandler(), EVENT_VOICE_CALL_ENDED, null); + if (mPhone.getCallTracker() != null) { + mPhone.getCallTracker().registerForVoiceCallStarted( + getHandler(), EVENT_VOICE_CALL_STARTED, null); + mPhone.getCallTracker().registerForVoiceCallEnded( + getHandler(), EVENT_VOICE_CALL_ENDED, null); + } // Check null for devices not supporting FEATURE_TELEPHONY_IMS. - if (mPhone.getImsPhone() != null) { + if (mPhone.getImsPhone() != null && mPhone.getImsPhone().getCallTracker() != null) { mPhone.getImsPhone().getCallTracker().registerForVoiceCallStarted( getHandler(), EVENT_VOICE_CALL_STARTED, null); mPhone.getImsPhone().getCallTracker().registerForVoiceCallEnded( @@ -1198,7 +1200,7 @@ public class DataNetwork extends StateMachine { getHandler()::post) { @Override public void onPreferredTransportChanged( - @NetCapability int networkCapability) { + @NetCapability int networkCapability, boolean forceReconnect) { if (networkCapability == NetworkCapabilities.NET_CAPABILITY_MMS) { log("MMS preference changed."); updateNetworkCapabilities(); @@ -1223,12 +1225,14 @@ public class DataNetwork extends StateMachine { } // Check null for devices not supporting FEATURE_TELEPHONY_IMS. - if (mPhone.getImsPhone() != null) { + if (mPhone.getImsPhone() != null && mPhone.getImsPhone().getCallTracker() != null) { mPhone.getImsPhone().getCallTracker().unregisterForVoiceCallStarted(getHandler()); mPhone.getImsPhone().getCallTracker().unregisterForVoiceCallEnded(getHandler()); } - mPhone.getCallTracker().unregisterForVoiceCallStarted(getHandler()); - mPhone.getCallTracker().unregisterForVoiceCallEnded(getHandler()); + if (mPhone.getCallTracker() != null) { + mPhone.getCallTracker().unregisterForVoiceCallStarted(getHandler()); + mPhone.getCallTracker().unregisterForVoiceCallEnded(getHandler()); + } mPhone.getServiceStateTracker().unregisterForCssIndicatorChanged(getHandler()); TelephonyManager tm = mPhone.getContext().getSystemService(TelephonyManager.class); @@ -1407,7 +1411,14 @@ public class DataNetwork extends StateMachine { } else { loge("Failed to allocate PDU session id. e=" + ar.exception); } - setupData(); + //Check whether all network requests were removed before setupData. + if (!mAttachedNetworkRequestList.isEmpty()) { + setupData(); + } else { + mRetryDelayMillis = DataCallResponse.RETRY_DURATION_UNDEFINED; + mFailCause = DataFailCause.NO_RETRY_FAILURE; + transitionTo(mDisconnectedState); + } break; case EVENT_SETUP_DATA_NETWORK_RESPONSE: int resultCode = msg.arg1; @@ -1536,7 +1547,7 @@ public class DataNetwork extends StateMachine { // For requests that can't be satisfied anymore, we need to put them back to the // unsatisfied pool. If none of network requests can be satisfied, then there is no // need to mark network agent connected. Just silently deactivate the data network. - if (mAttachedNetworkRequestList.size() == 0) { + if (mAttachedNetworkRequestList.isEmpty()) { log("Tear down the network since there is no live network request."); // Directly call onTearDown here. Calling tearDown will cause deadlock because // EVENT_TEAR_DOWN_NETWORK is deferred until state machine enters connected @@ -2507,7 +2518,8 @@ public class DataNetwork extends StateMachine { newSuspendedState = true; // Check voice/data concurrency. } else if (!mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed() - && mTransport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) { + && mTransport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN + && mPhone.getCallTracker() != null) { newSuspendedState = mPhone.getCallTracker().getState() != PhoneConstants.State.IDLE; } diff --git a/src/java/com/android/internal/telephony/data/DataNetworkController.java b/src/java/com/android/internal/telephony/data/DataNetworkController.java index 2fb23a738e..70d3b23142 100644 --- a/src/java/com/android/internal/telephony/data/DataNetworkController.java +++ b/src/java/com/android/internal/telephony/data/DataNetworkController.java @@ -26,6 +26,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.net.NetworkAgent; import android.net.NetworkCapabilities; import android.net.NetworkPolicyManager; @@ -426,6 +427,12 @@ public class DataNetworkController extends Handler { } }; + private boolean hasCalling() { + if (!mFeatureFlags.minimalTelephonyCdmCheck()) return true; + return mPhone.getContext().getPackageManager().hasSystemFeature( + PackageManager.FEATURE_TELEPHONY_CALLING); + } + /** * The sorted network request list by priority. The highest priority network request stays at * the head of the list. The highest priority is 100, the lowest is 0. @@ -861,7 +868,7 @@ public class DataNetworkController extends Handler { mDataSettingsManager = TelephonyComponentFactory.getInstance().inject( DataSettingsManager.class.getName()) - .makeDataSettingsManager(mPhone, this, looper, + .makeDataSettingsManager(mPhone, this, mFeatureFlags, looper, new DataSettingsManagerCallback(this::post) { @Override public void onDataEnabledChanged(boolean enabled, @@ -921,7 +928,7 @@ public class DataNetworkController extends Handler { } }); mDataStallRecoveryManager = new DataStallRecoveryManager(mPhone, this, mDataServiceManagers - .get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN), looper, + .get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN), mFeatureFlags, looper, new DataStallRecoveryManagerCallback(this::post) { @Override public void onDataStallReestablishInternet() { @@ -975,13 +982,16 @@ public class DataNetworkController extends Handler { mAccessNetworksManager.registerCallback(new AccessNetworksManagerCallback(this::post) { @Override - public void onPreferredTransportChanged(@NetCapability int capability) { + public void onPreferredTransportChanged( + @NetCapability int capability, boolean forceReconnect) { int preferredTransport = mAccessNetworksManager .getPreferredTransportByNetworkCapability(capability); logl("onPreferredTransportChanged: " + DataUtils.networkCapabilityToString(capability) + " preferred on " - + AccessNetworkConstants.transportTypeToString(preferredTransport)); - DataNetworkController.this.onEvaluatePreferredTransport(capability); + + AccessNetworkConstants.transportTypeToString(preferredTransport) + + (forceReconnect ? "forceReconnect:true" : "")); + + DataNetworkController.this.onEvaluatePreferredTransport(capability, forceReconnect); if (!hasMessages(EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS)) { sendMessage(obtainMessage(EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS, DataEvaluationReason.PREFERRED_TRANSPORT_CHANGED)); @@ -1042,16 +1052,18 @@ public class DataNetworkController extends Handler { } }, this::post); - // Register for call ended event for voice/data concurrent not supported case. It is - // intended to only listen for events from the same phone as most of the telephony modules - // are designed as per-SIM basis. For DSDS call ended on non-DDS sub, the frameworks relies - // on service state on DDS sub change from out-of-service to in-service to trigger data - // retry. - mPhone.getCallTracker().registerForVoiceCallEnded(this, EVENT_VOICE_CALL_ENDED, null); - // Check null for devices not supporting FEATURE_TELEPHONY_IMS. - if (mPhone.getImsPhone() != null) { - mPhone.getImsPhone().getCallTracker().registerForVoiceCallEnded( - this, EVENT_VOICE_CALL_ENDED, null); + if (hasCalling()) { + // Register for call ended event for voice/data concurrent not supported case. It is + // intended to only listen for events from the same phone as most of the telephony + // modules are designed as per-SIM basis. For DSDS call ended on non-DDS sub, the + // frameworks relies on service state on DDS sub change from out-of-service to + // in-service to trigger data retry. + mPhone.getCallTracker().registerForVoiceCallEnded(this, EVENT_VOICE_CALL_ENDED, null); + // Check null for devices not supporting FEATURE_TELEPHONY_IMS. + if (mPhone.getImsPhone() != null) { + mPhone.getImsPhone().getCallTracker().registerForVoiceCallEnded( + this, EVENT_VOICE_CALL_ENDED, null); + } } mPhone.mCi.registerForSlicingConfigChanged(this, EVENT_SLICE_CONFIG_CHANGED, null); mPhone.mCi.registerForSrvccStateChanged(this, EVENT_SRVCC_STATE_CHANGED, null); @@ -1165,7 +1177,7 @@ public class DataNetworkController extends Handler { } break; case EVENT_EVALUATE_PREFERRED_TRANSPORT: - onEvaluatePreferredTransport(msg.arg1); + onEvaluatePreferredTransport(msg.arg1, msg.arg2 != 0 /* forceReconnect */); break; case EVENT_SUBSCRIPTION_PLANS_CHANGED: SubscriptionPlan[] plans = (SubscriptionPlan[]) msg.obj; @@ -1566,7 +1578,7 @@ public class DataNetworkController extends Handler { } // Check CS call state and see if concurrent voice/data is allowed. - if (mPhone.getCallTracker().getState() != PhoneConstants.State.IDLE + if (hasCalling() && mPhone.getCallTracker().getState() != PhoneConstants.State.IDLE && !mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) { evaluation.addDataDisallowedReason( DataDisallowedReason.CONCURRENT_VOICE_DATA_NOT_ALLOWED); @@ -3359,8 +3371,12 @@ public class DataNetworkController extends Handler { * Called when needed to evaluate the preferred transport for certain capability. * * @param capability The network capability to evaluate. + * @param forceReconnect indicates whether enforce reconnection to move to the preferred + * transport type. + * */ - private void onEvaluatePreferredTransport(@NetCapability int capability) { + private void onEvaluatePreferredTransport( + @NetCapability int capability, boolean forceReconnect) { int preferredTransport = mAccessNetworksManager .getPreferredTransportByNetworkCapability(capability); log("onEvaluatePreferredTransport: " + DataUtils.networkCapabilityToString(capability) @@ -3384,7 +3400,13 @@ public class DataNetworkController extends Handler { continue; } - tryHandoverDataNetwork(dataNetwork, preferredTransport, null/*handoverRetryEntry*/); + if (forceReconnect) { + tearDownGracefully( + dataNetwork, DataNetwork.TEAR_DOWN_REASON_HANDOVER_NOT_ALLOWED); + } else { + tryHandoverDataNetwork( + dataNetwork, preferredTransport, null/*handoverRetryEntry*/); + } } } } diff --git a/src/java/com/android/internal/telephony/data/DataSettingsManager.java b/src/java/com/android/internal/telephony/data/DataSettingsManager.java index e54f6d382e..51e5b7d8f1 100644 --- a/src/java/com/android/internal/telephony/data/DataSettingsManager.java +++ b/src/java/com/android/internal/telephony/data/DataSettingsManager.java @@ -20,6 +20,7 @@ import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.content.ContentResolver; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -47,6 +48,7 @@ import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.SettingsObserver; import com.android.internal.telephony.data.DataConfigManager.DataConfigManagerCallback; +import com.android.internal.telephony.flags.FeatureFlags; import com.android.internal.telephony.metrics.DeviceTelephonyPropertiesStats; import com.android.internal.telephony.subscription.SubscriptionInfoInternal; import com.android.internal.telephony.subscription.SubscriptionManagerService; @@ -57,6 +59,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.HashSet; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; import java.util.stream.Collectors; @@ -88,6 +91,7 @@ public class DataSettingsManager extends Handler { private static final int EVENT_INITIALIZE = 11; private final Phone mPhone; + private final @NonNull FeatureFlags mFeatureFlags; private final ContentResolver mResolver; private final SettingsObserver mSettingsObserver; private final String mLogTag; @@ -178,10 +182,12 @@ public class DataSettingsManager extends Handler { * @param callback Data settings manager callback. */ public DataSettingsManager(@NonNull Phone phone, - @NonNull DataNetworkController dataNetworkController, @NonNull Looper looper, + @NonNull DataNetworkController dataNetworkController, + @NonNull FeatureFlags featureFlags, @NonNull Looper looper, @NonNull DataSettingsManagerCallback callback) { super(looper); mPhone = phone; + mFeatureFlags = Objects.requireNonNull(featureFlags); mLogTag = "DSMGR-" + mPhone.getPhoneId(); log("DataSettingsManager created."); mSubId = mPhone.getSubId(); @@ -264,6 +270,12 @@ public class DataSettingsManager extends Handler { } } + private boolean hasCalling() { + if (!mFeatureFlags.minimalTelephonyCdmCheck()) return true; + return mPhone.getContext().getPackageManager().hasSystemFeature( + PackageManager.FEATURE_TELEPHONY_CALLING); + } + /** * Called when needed to register for all events that data network controller is interested. */ @@ -281,9 +293,12 @@ public class DataSettingsManager extends Handler { mSettingsObserver.observe( Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED), EVENT_PROVISIONING_DATA_ENABLED_CHANGED); - mPhone.getCallTracker().registerForVoiceCallStarted(this, EVENT_CALL_STATE_CHANGED, null); - mPhone.getCallTracker().registerForVoiceCallEnded(this, EVENT_CALL_STATE_CHANGED, null); - if (mPhone.getImsPhone() != null) { + if (hasCalling()) { + mPhone.getCallTracker().registerForVoiceCallStarted(this, EVENT_CALL_STATE_CHANGED, + null); + mPhone.getCallTracker().registerForVoiceCallEnded(this, EVENT_CALL_STATE_CHANGED, null); + } + if (hasCalling() && mPhone.getImsPhone() != null) { mPhone.getImsPhone().getCallTracker().registerForVoiceCallStarted( this, EVENT_CALL_STATE_CHANGED, null); mPhone.getImsPhone().getCallTracker().registerForVoiceCallEnded( diff --git a/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java b/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java index 893509cc98..ee8890aebd 100644 --- a/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java +++ b/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java @@ -48,6 +48,7 @@ import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.data.DataConfigManager.DataConfigManagerCallback; import com.android.internal.telephony.data.DataNetworkController.DataNetworkControllerCallback; import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback; +import com.android.internal.telephony.flags.FeatureFlags; import com.android.internal.telephony.metrics.DataStallRecoveryStats; import com.android.internal.telephony.metrics.TelephonyMetrics; import com.android.telephony.Rlog; @@ -153,6 +154,7 @@ public class DataStallRecoveryManager extends Handler { private final @NonNull Phone mPhone; private final @NonNull String mLogTag; private final @NonNull LocalLog mLocalLog = new LocalLog(128); + private final @NonNull FeatureFlags mFeatureFlags; /** Data network controller */ private final @NonNull DataNetworkController mDataNetworkController; @@ -196,7 +198,10 @@ public class DataStallRecoveryManager extends Handler { private boolean mIsInternetNetworkConnected; /** The durations for current recovery action */ private @ElapsedRealtimeLong long mTimeElapsedOfCurrentAction; - + /** Tracks the total number of validation duration a data stall */ + private int mValidationCount; + /** Tracks the number of validation for current action during a data stall */ + private int mActionValidationCount; /** The array for the timers between recovery actions. */ private @NonNull long[] mDataStallRecoveryDelayMillisArray; /** The boolean array for the flags. They are used to skip the recovery actions if needed. */ @@ -253,6 +258,7 @@ public class DataStallRecoveryManager extends Handler { * @param phone The phone instance. * @param dataNetworkController Data network controller * @param dataServiceManager The WWAN data service manager. + * @param featureFlags The feature flag. * @param looper The looper to be used by the handler. Currently the handler thread is the phone * process's main thread. * @param callback Callback to notify data network controller for data stall events. @@ -261,6 +267,7 @@ public class DataStallRecoveryManager extends Handler { @NonNull Phone phone, @NonNull DataNetworkController dataNetworkController, @NonNull DataServiceManager dataServiceManager, + @NonNull FeatureFlags featureFlags, @NonNull Looper looper, @NonNull DataStallRecoveryManagerCallback callback) { super(looper); @@ -269,6 +276,7 @@ public class DataStallRecoveryManager extends Handler { log("DataStallRecoveryManager created."); mDataNetworkController = dataNetworkController; mWwanDataServiceManager = dataServiceManager; + mFeatureFlags = featureFlags; mDataConfigManager = mDataNetworkController.getDataConfigManager(); mDataNetworkController .getDataSettingsManager() @@ -288,7 +296,7 @@ public class DataStallRecoveryManager extends Handler { registerAllEvents(); - mStats = new DataStallRecoveryStats(mPhone, dataNetworkController); + mStats = new DataStallRecoveryStats(mPhone, mFeatureFlags, dataNetworkController); } /** Register for all events that data stall monitor is interested. */ @@ -546,6 +554,8 @@ public class DataStallRecoveryManager extends Handler { mTimeLastRecoveryStartMs = 0; mLastAction = RECOVERY_ACTION_GET_DATA_CALL_LIST; mRecoveryAction = RECOVERY_ACTION_GET_DATA_CALL_LIST; + mValidationCount = 0; + mActionValidationCount = 0; } /** @@ -556,8 +566,16 @@ public class DataStallRecoveryManager extends Handler { private void onInternetValidationStatusChanged(@ValidationStatus int status) { logl("onInternetValidationStatusChanged: " + DataUtils.validationStatusToString(status)); final boolean isValid = status == NetworkAgent.VALIDATION_STATUS_VALID; + if (mFeatureFlags.dsrsDiagnosticsEnabled()) { + mValidationCount += 1; + mActionValidationCount += 1; + } setNetworkValidationState(isValid); if (isValid) { + if (mFeatureFlags.dsrsDiagnosticsEnabled()) { + // Broadcast intent that data stall recovered. + broadcastDataStallDetected(getRecoveryAction()); + } reset(); } else if (isRecoveryNeeded(true)) { // Set the network as invalid, because recovery is needed @@ -596,6 +614,10 @@ public class DataStallRecoveryManager extends Handler { */ @VisibleForTesting public void setRecoveryAction(@RecoveryAction int action) { + // Reset the validation count for action change + if (mFeatureFlags.dsrsDiagnosticsEnabled() && mRecoveryAction != action) { + mActionValidationCount = 0; + } mRecoveryAction = action; // Check if the mobile data enabled is TRUE, it means that the mobile data setting changed @@ -674,13 +696,16 @@ public class DataStallRecoveryManager extends Handler { final boolean isRecovered = !mDataStalled; final int duration = (int) (SystemClock.elapsedRealtime() - mDataStallStartMs); final @RecoveredReason int reason = getRecoveredReason(mIsValidNetwork); - final boolean isFirstValidationOfAction = false; final int durationOfAction = (int) getDurationOfCurrentRecoveryMs(); + if (mFeatureFlags.dsrsDiagnosticsEnabled()) { + log("mValidationCount=" + mValidationCount + + ", mActionValidationCount=" + mActionValidationCount); + } // Get the bundled DSRS stats. Bundle bundle = mStats.getDataStallRecoveryMetricsData( - recoveryAction, isRecovered, duration, reason, isFirstValidationOfAction, - durationOfAction); + recoveryAction, isRecovered, duration, reason, mValidationCount, + mActionValidationCount, durationOfAction); // Put the bundled stats extras on the intent. intent.putExtra("EXTRA_DSRS_STATS_BUNDLE", bundle); diff --git a/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java index e0e0354552..66b977d7ca 100644 --- a/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java +++ b/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java @@ -37,10 +37,12 @@ import android.telephony.Annotation.NetCapability; import android.telephony.DomainSelectionService; import android.telephony.EmergencyRegistrationResult; import android.telephony.NetworkRegistrationInfo; +import android.telephony.PreciseDisconnectCause; import android.telephony.data.ApnSetting; import android.telephony.ims.ImsReasonInfo; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.CallFailCause; import com.android.internal.telephony.Phone; import com.android.internal.telephony.data.AccessNetworksManager; import com.android.internal.telephony.data.AccessNetworksManager.QualifiedNetworks; @@ -162,7 +164,8 @@ public class EmergencyCallDomainSelectionConnection extends DomainSelectionConne private AccessNetworksManager.AccessNetworksManagerCallback mPreferredTransportCallback = new AccessNetworksManager.AccessNetworksManagerCallback(Runnable::run) { @Override - public void onPreferredTransportChanged(@NetCapability int capability) { + public void onPreferredTransportChanged( + @NetCapability int capability, boolean forceReconnect) { } }; @@ -214,6 +217,19 @@ public class EmergencyCallDomainSelectionConnection extends DomainSelectionConne @NonNull String callId, @NonNull String number, boolean isTest, int callFailCause, @Nullable ImsReasonInfo imsReasonInfo, @Nullable EmergencyRegistrationResult emergencyRegResult) { + + int preciseDisconnectCause = callFailCause; + switch (callFailCause) { + case CallFailCause.IMS_EMERGENCY_TEMP_FAILURE: + preciseDisconnectCause = PreciseDisconnectCause.EMERGENCY_TEMP_FAILURE; + break; + case CallFailCause.IMS_EMERGENCY_PERM_FAILURE: + preciseDisconnectCause = PreciseDisconnectCause.EMERGENCY_PERM_FAILURE; + break; + default: + break; + } + DomainSelectionService.SelectionAttributes.Builder builder = new DomainSelectionService.SelectionAttributes.Builder( slotId, subId, SELECTOR_TYPE_CALLING) @@ -222,7 +238,7 @@ public class EmergencyCallDomainSelectionConnection extends DomainSelectionConne .setExitedFromAirplaneMode(exited) .setCallId(callId) .setAddress(Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null)) - .setCsDisconnectCause(callFailCause); + .setCsDisconnectCause(preciseDisconnectCause); if (imsReasonInfo != null) builder.setPsDisconnectCause(imsReasonInfo); if (emergencyRegResult != null) builder.setEmergencyRegistrationResult(emergencyRegResult); diff --git a/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java b/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java index e2418c5acd..02dd613b88 100644 --- a/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java +++ b/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java @@ -16,10 +16,12 @@ package com.android.internal.telephony.emergency; +import android.annotation.NonNull; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.content.res.Resources; import android.os.AsyncResult; import android.os.Environment; @@ -48,6 +50,7 @@ import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.ServiceStateTracker; +import com.android.internal.telephony.flags.FeatureFlags; import com.android.internal.telephony.metrics.EmergencyNumberStats; import com.android.internal.telephony.metrics.TelephonyMetrics; import com.android.internal.telephony.nano.PersistAtomsProto; @@ -102,6 +105,7 @@ public class EmergencyNumberTracker extends Handler { private final CommandsInterface mCi; private final Phone mPhone; + private final @NonNull FeatureFlags mFeatureFlags; private int mPhoneId; private String mCountryIso; private String mLastKnownEmergencyCountryIso = ""; @@ -173,10 +177,20 @@ public class EmergencyNumberTracker extends Handler { } }; - public EmergencyNumberTracker(Phone phone, CommandsInterface ci) { + public EmergencyNumberTracker(Phone phone, CommandsInterface ci, + @NonNull FeatureFlags featureFlags) { + Context ctx = phone.getContext(); + mPhone = phone; mCi = ci; - mResources = mPhone.getContext().getResources(); + mFeatureFlags = featureFlags; + mResources = ctx.getResources(); + + if (mFeatureFlags.minimalTelephonyCdmCheck() + && !ctx.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_TELEPHONY_CALLING)) { + throw new UnsupportedOperationException("EmergencyNumberTracker requires calling"); + } if (mPhone != null) { mPhoneId = phone.getPhoneId(); diff --git a/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java b/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java index a35cccff09..c4d5355734 100644 --- a/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java +++ b/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java @@ -46,12 +46,9 @@ import android.telephony.CarrierConfigManager; import android.telephony.DisconnectCause; import android.telephony.EmergencyRegistrationResult; import android.telephony.NetworkRegistrationInfo; -import android.telephony.PreciseDataConnectionState; import android.telephony.ServiceState; import android.telephony.SubscriptionManager; -import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; -import android.telephony.data.ApnSetting; import android.util.ArraySet; import com.android.internal.annotations.VisibleForTesting; @@ -71,7 +68,6 @@ import java.util.Arrays; import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; import java.util.function.Consumer; /** @@ -92,7 +88,6 @@ public class EmergencyStateTracker { 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; @@ -132,9 +127,6 @@ public class EmergencyStateTracker { private final Runnable mExitEcmRunnable = this::exitEmergencyCallbackMode; // Tracks emergency calls by callId that have reached {@link Call.State#ACTIVE}. 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 connection to handle a second emergency call private android.telecom.Connection mOngoingConnection; @@ -152,6 +144,9 @@ public class EmergencyStateTracker { private Phone mSmsPhone; private CompletableFuture<Integer> mSmsEmergencyModeFuture; private boolean mIsTestEmergencyNumberForSms; + // For tracking the emergency SMS callback mode. + private boolean mIsInScbm; + private boolean mIsEmergencySmsStartedDuringScbm; private CompletableFuture<Boolean> mEmergencyTransportChangedFuture; @@ -182,29 +177,6 @@ public class EmergencyStateTracker { } }; - /** - * TelephonyCallback used to monitor whether ePDN on cellular network is disconnected or not. - */ - private final class PreciseDataConnectionStateListener extends TelephonyCallback implements - TelephonyCallback.PreciseDataConnectionStateListener { - @Override - public void onPreciseDataConnectionStateChanged( - @NonNull PreciseDataConnectionState dataConnectionState) { - ApnSetting apnSetting = dataConnectionState.getApnSetting(); - if ((apnSetting == null) - || ((apnSetting.getApnTypeBitmask() | ApnSetting.TYPE_EMERGENCY) == 0) - || (dataConnectionState.getTransportType() - != AccessNetworkConstants.TRANSPORT_TYPE_WWAN)) { - return; - } - int state = dataConnectionState.getState(); - Rlog.d(TAG, "onPreciseDataConnectionStateChanged ePDN state=" + state); - if (state == TelephonyManager.DATA_DISCONNECTED) exitEmergencyModeIfDelayed(); - } - } - - private PreciseDataConnectionStateListener mDataConnectionStateListener; - /** PhoneFactory Dependencies for testing. */ @VisibleForTesting public interface PhoneFactoryProxy { @@ -228,8 +200,6 @@ public class EmergencyStateTracker { @VisibleForTesting public interface TelephonyManagerProxy { int getPhoneCount(); - void registerTelephonyCallback(int subId, Executor executor, TelephonyCallback callback); - void unregisterTelephonyCallback(TelephonyCallback callback); } private final TelephonyManagerProxy mTelephonyManagerProxy; @@ -245,18 +215,6 @@ public class EmergencyStateTracker { public int getPhoneCount() { return mTelephonyManager.getActiveModemCount(); } - - @Override - public void registerTelephonyCallback(int subId, - Executor executor, TelephonyCallback callback) { - TelephonyManager tm = mTelephonyManager.createForSubscriptionId(subId); - tm.registerTelephonyCallback(executor, callback); - } - - @Override - public void unregisterTelephonyCallback(TelephonyCallback callback) { - mTelephonyManager.unregisterTelephonyCallback(callback); - } } /** @@ -268,15 +226,13 @@ public class EmergencyStateTracker { } @VisibleForTesting - public static final int MSG_SET_EMERGENCY_MODE = 1; - @VisibleForTesting - public static final int MSG_EXIT_EMERGENCY_MODE = 2; - @VisibleForTesting - public static final int MSG_SET_EMERGENCY_MODE_DONE = 3; + public static final int MSG_SET_EMERGENCY_MODE_DONE = 1; @VisibleForTesting - public static final int MSG_EXIT_EMERGENCY_MODE_DONE = 4; + public static final int MSG_EXIT_EMERGENCY_MODE_DONE = 2; @VisibleForTesting - public static final int MSG_SET_EMERGENCY_CALLBACK_MODE_DONE = 5; + public static final int MSG_SET_EMERGENCY_CALLBACK_MODE_DONE = 3; + /** A message which is used to automatically exit from SCBM after a period of time. */ + private static final int MSG_EXIT_SCBM = 4; private class MyHandler extends Handler { @@ -312,38 +268,46 @@ public class EmergencyStateTracker { // Case 1) When the emergency call is setting the emergency mode and // the emergency SMS is being sent, completes the SMS future also. // Case 2) When the emergency SMS is setting the emergency mode and - // the emergency call is beint started, the SMS request is cancelled and + // the emergency call is being started, the SMS request is cancelled and // the call request will be handled. if (mSmsPhone != null) { completeEmergencyMode(EMERGENCY_TYPE_SMS); } } else if (emergencyType == EMERGENCY_TYPE_SMS) { if (mPhone != null && mSmsPhone != null) { - // Clear call phone temporarily to exit the emergency mode - // if the emergency call is started. if (mIsEmergencyCallStartedDuringEmergencySms) { - Phone phone = mPhone; - mPhone = null; - exitEmergencyMode(mSmsPhone, emergencyType, false); - // Restore call phone for further use. - mPhone = phone; - - if (!isSamePhone(mPhone, mSmsPhone)) { - completeEmergencyMode(emergencyType, - DisconnectCause.OUTGOING_EMERGENCY_CALL_PLACED); + if (!isSamePhone(mPhone, mSmsPhone) || !isInScbm()) { + // Clear call phone temporarily to exit the emergency mode + // if the emergency call is started. + Phone phone = mPhone; + mPhone = null; + exitEmergencyMode(mSmsPhone, emergencyType); + // Restore call phone for further use. + mPhone = phone; + if (!isSamePhone(mPhone, mSmsPhone)) { + completeEmergencyMode(emergencyType, + DisconnectCause.OUTGOING_EMERGENCY_CALL_PLACED); + exitEmergencySmsCallbackMode(); + } + } else { + completeEmergencyMode(emergencyType); + mIsEmergencyCallStartedDuringEmergencySms = false; + exitEmergencySmsCallbackMode(); + turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL, + mIsTestEmergencyNumber); } } else { completeEmergencyMode(emergencyType); } - break; } else { completeEmergencyMode(emergencyType); - } - if (mIsEmergencyCallStartedDuringEmergencySms) { - mIsEmergencyCallStartedDuringEmergencySms = false; - turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL, - mIsTestEmergencyNumber); + if (mIsEmergencyCallStartedDuringEmergencySms) { + mIsEmergencyCallStartedDuringEmergencySms = false; + exitEmergencySmsCallbackMode(); + turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL, + mIsTestEmergencyNumber); + } } } break; @@ -366,6 +330,10 @@ public class EmergencyStateTracker { mIsEmergencyCallStartedDuringEmergencySms = false; turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL, mIsTestEmergencyNumber); + } else if (mIsEmergencySmsStartedDuringScbm) { + mIsEmergencySmsStartedDuringScbm = false; + setEmergencyMode(mSmsPhone, emergencyType, + MODE_EMERGENCY_WWAN, MSG_SET_EMERGENCY_MODE_DONE); } } break; @@ -378,30 +346,35 @@ public class EmergencyStateTracker { setEmergencyModeInProgress(false); // When the emergency callback mode is in progress and the emergency SMS is // started, it needs to be completed here for the emergency SMS. - if (mSmsPhone != null) { - completeEmergencyMode(EMERGENCY_TYPE_SMS); + if (emergencyType == EMERGENCY_TYPE_CALL) { + if (mSmsPhone != null) { + completeEmergencyMode(EMERGENCY_TYPE_SMS); + } + } else if (emergencyType == EMERGENCY_TYPE_SMS) { + // When the emergency SMS callback mode is in progress on other phone and + // the emergency call was started, needs to exit the emergency mode first. + if (mIsEmergencyCallStartedDuringEmergencySms) { + final Phone smsPhone = mSmsPhone; + exitEmergencySmsCallbackMode(); + + if (mPhone != null && smsPhone != null + && !isSamePhone(mPhone, smsPhone)) { + Phone phone = mPhone; + mPhone = null; + exitEmergencyMode(smsPhone, emergencyType); + // Restore call phone for further use. + mPhone = phone; + } else { + mIsEmergencyCallStartedDuringEmergencySms = false; + turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL, + mIsTestEmergencyNumber); + } + } } break; } - case MSG_EXIT_EMERGENCY_MODE: { - Rlog.v(TAG, "MSG_EXIT_EMERGENCY_MODE"); - exitEmergencyModeIfDelayed(); - break; - } - case MSG_SET_EMERGENCY_MODE: { - AsyncResult ar = (AsyncResult) msg.obj; - Integer emergencyType = (Integer) ar.userObj; - Rlog.v(TAG, "MSG_SET_EMERGENCY_MODE for " - + emergencyTypeToString(emergencyType) + ", " + mEmergencyMode); - // Should be reached here only when starting a new emergency service - // while exiting emergency callback mode on the other slot. - if (mEmergencyMode != MODE_EMERGENCY_WWAN) return; - final Phone phone = (mPhone != null) ? mPhone : mSmsPhone; - if (phone != null) { - mWasEmergencyModeSetOnModem = true; - phone.setEmergencyMode(MODE_EMERGENCY_WWAN, - mHandler.obtainMessage(MSG_SET_EMERGENCY_MODE_DONE, emergencyType)); - } + case MSG_EXIT_SCBM: { + exitEmergencySmsCallbackModeAndEmergencyMode(); break; } default: @@ -522,19 +495,26 @@ public class EmergencyStateTracker { // 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())) { + exitEmergencySmsCallbackMode(); mOngoingConnection = c; mIsTestEmergencyNumber = isTestEmergencyNumber; - // Ensure that domain selector requests scan. - mLastEmergencyRegistrationResult = new EmergencyRegistrationResult( - AccessNetworkConstants.AccessNetworkType.UNKNOWN, - NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN, - NetworkRegistrationInfo.DOMAIN_UNKNOWN, false, false, 0, 0, "", "", ""); if (isInEcm()) { // Remove pending exit ECM runnable. mHandler.removeCallbacks(mExitEcmRunnable); releaseWakeLock(); ((GsmCdmaPhone) mPhone).notifyEcbmTimerReset(Boolean.TRUE); + + mOngoingCallProperties = 0; + mCallEmergencyModeFuture = new CompletableFuture<>(); + setEmergencyMode(mPhone, EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WWAN, + MSG_SET_EMERGENCY_MODE_DONE); + return mCallEmergencyModeFuture; } + // Ensure that domain selector requests scan. + mLastEmergencyRegistrationResult = new EmergencyRegistrationResult( + AccessNetworkConstants.AccessNetworkType.UNKNOWN, + NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN, + NetworkRegistrationInfo.DOMAIN_UNKNOWN, false, false, 0, 0, "", "", ""); return CompletableFuture.completedFuture(DisconnectCause.NOT_DISCONNECTED); } @@ -553,13 +533,25 @@ public class EmergencyStateTracker { // exit the emergency mode when receiving the result of setting the emergency mode and // the emergency mode for this call will be restarted after the exit complete. if (isInEmergencyMode() && !isEmergencyModeInProgress()) { - exitEmergencyMode(mSmsPhone, EMERGENCY_TYPE_SMS, false); + if (!isSamePhone(mSmsPhone, phone)) { + exitEmergencyMode(mSmsPhone, EMERGENCY_TYPE_SMS); + } else { + // If the device is already in the emergency mode on the same phone, + // the general emergency call procedure can be immediately performed. + // And, if the emergency PDN is already connected, then we need to keep + // this PDN active while initating the emergency call. + mIsEmergencyCallStartedDuringEmergencySms = false; + } + + exitEmergencySmsCallbackMode(); } - mPhone = phone; - mOngoingConnection = c; - mIsTestEmergencyNumber = isTestEmergencyNumber; - return mCallEmergencyModeFuture; + if (mIsEmergencyCallStartedDuringEmergencySms) { + mPhone = phone; + mOngoingConnection = c; + mIsTestEmergencyNumber = isTestEmergencyNumber; + return mCallEmergencyModeFuture; + } } mPhone = phone; @@ -587,7 +579,7 @@ public class EmergencyStateTracker { } if (wasActive && mActiveEmergencyCalls.isEmpty() - && isEmergencyCallbackModeSupported()) { + && isEmergencyCallbackModeSupported(mPhone)) { enterEmergencyCallbackMode(); if (mOngoingConnection == null) { @@ -604,7 +596,12 @@ public class EmergencyStateTracker { enterEmergencyCallbackMode(); } } else { - exitEmergencyMode(mPhone, EMERGENCY_TYPE_CALL, false); + if (isInScbm()) { + setIsInEmergencyCall(false); + setEmergencyCallbackMode(mSmsPhone, EMERGENCY_TYPE_SMS); + } else { + exitEmergencyMode(mPhone, EMERGENCY_TYPE_CALL); + } clearEmergencyCallInfo(); } } @@ -635,8 +632,20 @@ public class EmergencyStateTracker { // 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); + if (mEmergencyMode != MODE_EMERGENCY_WWAN) { + setEmergencyMode(phone, emergencyType, MODE_EMERGENCY_WWAN, + MSG_SET_EMERGENCY_MODE_DONE); + } else { + // Ensure that domain selector requests the network scan. + mLastEmergencyRegistrationResult = new EmergencyRegistrationResult( + AccessNetworkConstants.AccessNetworkType.UNKNOWN, + NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN, + NetworkRegistrationInfo.DOMAIN_UNKNOWN, false, false, 0, 0, "", "", ""); + if (emergencyType == EMERGENCY_TYPE_CALL) { + setIsInEmergencyCall(true); + } + completeEmergencyMode(emergencyType); + } }); } @@ -673,26 +682,33 @@ public class EmergencyStateTracker { return; } - synchronized (mLock) { - unregisterForDataConnectionStateChanges(); - if (mPhoneToExit != null) { - if (emergencyType != EMERGENCY_TYPE_CALL) { - setIsInEmergencyCall(false); - } - mOnEcmExitCompleteRunnable = null; - if (mPhoneToExit != phone) { - // Exit emergency mode on the other phone first, - // then set emergency mode on the given phone. - mPhoneToExit.exitEmergencyMode( - mHandler.obtainMessage(MSG_SET_EMERGENCY_MODE, - Integer.valueOf(emergencyType))); - mPhoneToExit = null; - return; - } - mPhoneToExit = null; + mWasEmergencyModeSetOnModem = true; + phone.setEmergencyMode(mode, m); + } + + /** + * Sets the emergency callback mode on modem. + * + * @param phone the {@code Phone} to set the emergency mode on modem. + * @param emergencyType the emergency type to identify an emergency call or SMS. + */ + private void setEmergencyCallbackMode(Phone phone, @EmergencyType int emergencyType) { + boolean needToSetCallbackMode = false; + + if (emergencyType == EMERGENCY_TYPE_CALL) { + needToSetCallbackMode = true; + } else if (emergencyType == EMERGENCY_TYPE_SMS) { + // Ensure that no emergency call is in progress. + if (mActiveEmergencyCalls.isEmpty() && mOngoingConnection == null + && mOngoingEmergencySmsIds.isEmpty()) { + needToSetCallbackMode = true; } - mWasEmergencyModeSetOnModem = true; - phone.setEmergencyMode(mode, m); + } + + if (needToSetCallbackMode) { + // Set emergency mode on modem. + setEmergencyMode(phone, emergencyType, MODE_EMERGENCY_CALLBACK, + MSG_SET_EMERGENCY_CALLBACK_MODE_DONE); } } @@ -766,10 +782,8 @@ public class EmergencyStateTracker { * * @param phone the {@code Phone} to exit the emergency mode. * @param emergencyType the emergency type to identify an emergency call or SMS. - * @param waitForPdnDisconnect indicates whether it shall wait for the disconnection of ePDN. */ - private void exitEmergencyMode(Phone phone, @EmergencyType int emergencyType, - boolean waitForPdnDisconnect) { + private void exitEmergencyMode(Phone phone, @EmergencyType int emergencyType) { Rlog.i(TAG, "exitEmergencyMode for " + emergencyTypeToString(emergencyType)); if (emergencyType == EMERGENCY_TYPE_CALL) { @@ -805,23 +819,8 @@ public class EmergencyStateTracker { return; } - synchronized (mLock) { - mWasEmergencyModeSetOnModem = false; - if (waitForPdnDisconnect) { - registerForDataConnectionStateChanges(phone); - mPhoneToExit = phone; - if (mPdnDisconnectionTimeoutMs > 0) { - // To avoid waiting for the disconnection indefinitely. - mHandler.sendEmptyMessageDelayed(MSG_EXIT_EMERGENCY_MODE, - mPdnDisconnectionTimeoutMs); - } - return; - } else { - unregisterForDataConnectionStateChanges(); - mPhoneToExit = null; - } - phone.exitEmergencyMode(m); - } + mWasEmergencyModeSetOnModem = false; + phone.exitEmergencyMode(m); } /** Returns last {@link EmergencyRegistrationResult} as set by {@code setEmergencyMode()}. */ @@ -985,12 +984,8 @@ public class EmergencyStateTracker { * Handles the radio power off request. */ public void onCellularRadioPowerOffRequested() { - synchronized (mLock) { - if (isInEcm()) { - exitEmergencyCallbackMode(null); - } - exitEmergencyModeIfDelayed(); - } + exitEmergencySmsCallbackModeAndEmergencyMode(); + exitEmergencyCallbackMode(); } private static boolean isVoWiFi(int properties) { @@ -1000,14 +995,16 @@ public class EmergencyStateTracker { /** * Returns {@code true} if device and carrier support emergency callback mode. + * + * @param phone The {@link Phone} instance to be checked. */ @VisibleForTesting - public boolean isEmergencyCallbackModeSupported() { - int subId = mPhone.getSubId(); + public boolean isEmergencyCallbackModeSupported(Phone phone) { + int subId = phone.getSubId(); if (!SubscriptionManager.isValidSubscriptionId(subId)) { // If there is no SIM, refer to the saved last carrier configuration with valid // subscription. - int phoneId = mPhone.getPhoneId(); + int phoneId = phone.getPhoneId(); Boolean savedConfig = mNoSimEcbmSupported.get(Integer.valueOf(phoneId)); if (savedConfig == null) { // Exceptional case such as with poor boot performance. @@ -1054,9 +1051,7 @@ public class EmergencyStateTracker { ((GsmCdmaPhone) mPhone).notifyEcbmTimerReset(Boolean.FALSE); } - // Set emergency mode on modem. - setEmergencyMode(mPhone, EMERGENCY_TYPE_CALL, MODE_EMERGENCY_CALLBACK, - MSG_SET_EMERGENCY_CALLBACK_MODE_DONE); + setEmergencyCallbackMode(mPhone, EMERGENCY_TYPE_CALL); // Post this runnable so we will automatically exit if no one invokes // exitEmergencyCallbackMode() directly. @@ -1091,9 +1086,7 @@ public class EmergencyStateTracker { gsmCdmaPhone.notifyEmergencyCallRegistrants(false); // Exit emergency mode on modem. - // b/299866883: Wait for the disconnection of ePDN before calling exitEmergencyMode. - exitEmergencyMode(gsmCdmaPhone, EMERGENCY_TYPE_CALL, - mEmergencyCallDomain == NetworkRegistrationInfo.DOMAIN_PS); + exitEmergencyMode(gsmCdmaPhone, EMERGENCY_TYPE_CALL); } mEmergencyCallDomain = NetworkRegistrationInfo.DOMAIN_UNKNOWN; @@ -1182,7 +1175,8 @@ public class EmergencyStateTracker { */ public CompletableFuture<Integer> startEmergencySms(@NonNull Phone phone, @NonNull String smsId, boolean isTestEmergencyNumber) { - Rlog.i(TAG, "startEmergencySms: phoneId=" + phone.getPhoneId() + ", smsId=" + smsId); + Rlog.i(TAG, "startEmergencySms: phoneId=" + phone.getPhoneId() + ", smsId=" + smsId + + ", scbm=" + isInScbm()); // When an emergency call is in progress, it checks whether an emergency call is already in // progress on the different phone. @@ -1191,17 +1185,29 @@ public class EmergencyStateTracker { return CompletableFuture.completedFuture(DisconnectCause.ERROR_UNSPECIFIED); } - // When an emergency SMS is in progress, it checks whether an emergency SMS is already in - // progress on the different phone. + boolean exitScbmInOtherPhone = false; + boolean smsStartedInScbm = isInScbm(); + + // When an emergency SMS is in progress, it checks whether an emergency SMS is already + // in progress on the different phone. if (mSmsPhone != null && !isSamePhone(mSmsPhone, phone)) { - Rlog.e(TAG, "Emergency SMS is in progress on the other slot."); - return CompletableFuture.completedFuture(DisconnectCause.ERROR_UNSPECIFIED); + if (smsStartedInScbm) { + // When other phone is in the emergency SMS callback mode, we need to stop the + // emergency SMS callback mode first. + exitScbmInOtherPhone = true; + mIsEmergencySmsStartedDuringScbm = true; + exitEmergencySmsCallbackModeAndEmergencyMode(); + } else { + Rlog.e(TAG, "Emergency SMS is in progress on the other slot."); + return CompletableFuture.completedFuture(DisconnectCause.ERROR_UNSPECIFIED); + } } - // When the previous emergency SMS is not completed yet, + // When the previous emergency SMS is not completed yet and the device is not in SCBM, // this new request will not be allowed. - if (mSmsPhone != null && isInEmergencyMode() && isEmergencyModeInProgress()) { - Rlog.e(TAG, "Existing emergency SMS is in progress."); + if (mSmsPhone != null && isInEmergencyMode() && isEmergencyModeInProgress() + && !smsStartedInScbm) { + Rlog.e(TAG, "Existing emergency SMS is in progress and not in SCBM."); return CompletableFuture.completedFuture(DisconnectCause.ERROR_UNSPECIFIED); } @@ -1209,17 +1215,31 @@ public class EmergencyStateTracker { mIsTestEmergencyNumberForSms = isTestEmergencyNumber; mOngoingEmergencySmsIds.add(smsId); - // When the emergency mode is already set by the previous emergency call or SMS, - // completes the future immediately. - if (isInEmergencyMode() && !isEmergencyModeInProgress()) { - return CompletableFuture.completedFuture(DisconnectCause.NOT_DISCONNECTED); - } + if (smsStartedInScbm) { + // When the device is in SCBM and emergency SMS is being sent, + // completes the future immediately. + if (!exitScbmInOtherPhone) { + // The emergency SMS is allowed and returns the success result. + return CompletableFuture.completedFuture(DisconnectCause.NOT_DISCONNECTED); + } - mSmsEmergencyModeFuture = new CompletableFuture<>(); - if (!isInEmergencyMode()) { - setEmergencyMode(mSmsPhone, EMERGENCY_TYPE_SMS, MODE_EMERGENCY_WWAN, - MSG_SET_EMERGENCY_MODE_DONE); + mSmsEmergencyModeFuture = new CompletableFuture<>(); + } else { + // When the emergency mode is already set by the previous emergency call or SMS, + // completes the future immediately. + if (isInEmergencyMode() && !isEmergencyModeInProgress()) { + // The emergency SMS is allowed and returns the success result. + return CompletableFuture.completedFuture(DisconnectCause.NOT_DISCONNECTED); + } + + mSmsEmergencyModeFuture = new CompletableFuture<>(); + + if (!isInEmergencyMode()) { + setEmergencyMode(mSmsPhone, EMERGENCY_TYPE_SMS, MODE_EMERGENCY_WWAN, + MSG_SET_EMERGENCY_MODE_DONE); + } } + return mSmsEmergencyModeFuture; } @@ -1230,35 +1250,142 @@ public class EmergencyStateTracker { * @param smsId the SMS id on which to end the emergency SMS. * @param success the flag specifying whether an emergency SMS is successfully sent or not. * {@code true} if SMS is successfully sent, {@code false} otherwise. + * @param domain the domain that MO SMS was sent. */ - public void endSms(@NonNull String smsId, boolean success) { + public void endSms(@NonNull String smsId, boolean success, + @NetworkRegistrationInfo.Domain int domain) { mOngoingEmergencySmsIds.remove(smsId); // If the outgoing emergency SMSs are empty, we can try to exit the emergency mode. if (mOngoingEmergencySmsIds.isEmpty()) { + mSmsEmergencyModeFuture = null; + mIsEmergencySmsStartedDuringScbm = false; + 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() && mOngoingConnection == null) { - setEmergencyMode(mPhone, EMERGENCY_TYPE_CALL, MODE_EMERGENCY_CALLBACK, - MSG_SET_EMERGENCY_CALLBACK_MODE_DONE); + setEmergencyCallbackMode(mPhone, EMERGENCY_TYPE_CALL); } + } + + // If SCBM supports, SCBM will be entered here regardless of ECBM state. + if (success && domain == NetworkRegistrationInfo.DOMAIN_PS + && (isInScbm() || isEmergencyCallbackModeSupported(mSmsPhone))) { + enterEmergencySmsCallbackMode(); + } else if (isInScbm()) { + // Sets the emergency mode to CALLBACK without re-initiating SCBM timer. + setEmergencyCallbackMode(mSmsPhone, EMERGENCY_TYPE_SMS); } else { - exitEmergencyMode(mSmsPhone, EMERGENCY_TYPE_SMS, false); + exitEmergencyMode(mSmsPhone, EMERGENCY_TYPE_SMS); + clearEmergencySmsInfo(); } + } + } - clearEmergencySmsInfo(); + /** + * Called when emergency SMS is received from the network. + */ + public void onEmergencySmsReceived() { + if (isInScbm()) { + Rlog.d(TAG, "Emergency SMS received, re-initiate SCBM timer"); + // Reinitiate the SCBM timer when receiving emergency SMS while in SCBM. + enterEmergencySmsCallbackMode(); } } private void clearEmergencySmsInfo() { mOngoingEmergencySmsIds.clear(); + mIsEmergencySmsStartedDuringScbm = false; mIsTestEmergencyNumberForSms = false; mSmsEmergencyModeFuture = null; mSmsPhone = null; } /** + * Returns {@code true} if currently in emergency SMS callback mode. + */ + public boolean isInScbm() { + return mIsInScbm; + } + + /** + * Sets the emergency SMS callback mode state. + * + * @param isInScbm {@code true} if currently in emergency SMS callback mode, + * {@code false} otherwise. + */ + private void setIsInScbm(boolean isInScbm) { + mIsInScbm = isInScbm; + } + + /** + * Enters the emergency SMS callback mode. + */ + private void enterEmergencySmsCallbackMode() { + Rlog.d(TAG, "enter SCBM while " + (isInScbm() ? "in" : "not in") + " SCBM"); + // Remove pending message if present. + mHandler.removeMessages(MSG_EXIT_SCBM); + + if (!isInScbm()) { + setIsInScbm(true); + } + + setEmergencyCallbackMode(mSmsPhone, EMERGENCY_TYPE_SMS); + + // At the moment, the default SCBM timer value will be used with the same value + // that is configured for emergency callback mode. + int delayInMillis = Long.valueOf(mEcmExitTimeoutMs).intValue(); + int subId = mSmsPhone.getSubId(); + if (SubscriptionManager.isValidSubscriptionId(subId)) { + delayInMillis = getConfig(subId, + CarrierConfigManager.KEY_EMERGENCY_SMS_MODE_TIMER_MS_INT, + delayInMillis); + if (delayInMillis == 0) { + delayInMillis = Long.valueOf(mEcmExitTimeoutMs).intValue(); + } + } + // Post the message so we will automatically exit if no one invokes + // exitEmergencySmsCallbackModeAndEmergencyMode() directly. + mHandler.sendEmptyMessageDelayed(MSG_EXIT_SCBM, delayInMillis); + } + + /** + * Exits emergency SMS callback mode and emergency mode if the device is in SCBM and + * the emergency mode is in CALLBACK. + */ + private void exitEmergencySmsCallbackModeAndEmergencyMode() { + Rlog.d(TAG, "exit SCBM and emergency mode"); + final Phone smsPhone = mSmsPhone; + boolean wasInScbm = isInScbm(); + exitEmergencySmsCallbackMode(); + + // The emergency mode needs to be checked to ensure that there is no ongoing emergency SMS. + if (wasInScbm && mOngoingEmergencySmsIds.isEmpty()) { + // Exit emergency mode on modem. + exitEmergencyMode(smsPhone, EMERGENCY_TYPE_SMS); + } + } + + /** + * Exits emergency SMS callback mode. + */ + private void exitEmergencySmsCallbackMode() { + // Remove pending message if present. + mHandler.removeMessages(MSG_EXIT_SCBM); + + if (isInScbm()) { + Rlog.i(TAG, "exit SCBM"); + setIsInScbm(false); + } + + if (mOngoingEmergencySmsIds.isEmpty()) { + mIsTestEmergencyNumberForSms = false; + mSmsPhone = null; + } + } + + /** * Returns {@code true} if any phones from PhoneFactory have radio on. */ private boolean isRadioOn() { @@ -1596,49 +1723,4 @@ public class EmergencyStateTracker { Rlog.i(TAG, "updateNoSimEcbmSupported preference updated slotIndex=" + slotIndex + ", supported=" + carrierConfig); } - - /** For test purpose only */ - @VisibleForTesting - public void setPdnDisconnectionTimeoutMs(int timeout) { - mPdnDisconnectionTimeoutMs = timeout; - } - - private void exitEmergencyModeIfDelayed() { - synchronized (mLock) { - if (mPhoneToExit != null) { - unregisterForDataConnectionStateChanges(); - mPhoneToExit.exitEmergencyMode( - mHandler.obtainMessage(MSG_EXIT_EMERGENCY_MODE_DONE, - Integer.valueOf(EMERGENCY_TYPE_CALL))); - mPhoneToExit = null; - } - } - } - - /** - * Registers for changes to data connection state. - */ - private void registerForDataConnectionStateChanges(Phone phone) { - if ((mDataConnectionStateListener != null) || (phone == null)) { - return; - } - Rlog.i(TAG, "registerForDataConnectionStateChanges"); - - mDataConnectionStateListener = new PreciseDataConnectionStateListener(); - mTelephonyManagerProxy.registerTelephonyCallback(phone.getSubId(), - mHandler::post, mDataConnectionStateListener); - } - - /** - * Unregisters for changes to data connection state. - */ - private void unregisterForDataConnectionStateChanges() { - if (mDataConnectionStateListener == null) { - return; - } - Rlog.i(TAG, "unregisterForDataConnectionStateChanges"); - - mTelephonyManagerProxy.unregisterTelephonyCallback(mDataConnectionStateListener); - mDataConnectionStateListener = null; - } } diff --git a/src/java/com/android/internal/telephony/euicc/EuiccConnector.java b/src/java/com/android/internal/telephony/euicc/EuiccConnector.java index 3c444efdda..4e773f3c4c 100644 --- a/src/java/com/android/internal/telephony/euicc/EuiccConnector.java +++ b/src/java/com/android/internal/telephony/euicc/EuiccConnector.java @@ -215,6 +215,11 @@ public class EuiccConnector extends StateMachine implements ServiceConnection { public interface GetAvailableMemoryInBytesCommandCallback extends BaseEuiccCommandCallback { /** Called when the available memory in bytes lookup has completed. */ void onGetAvailableMemoryInBytesComplete(long availableMemoryInBytes); + /** + * Called when the connected LPA does not implement + * EuiccService#onGetAvailableMemoryInBytes(int). + */ + void onUnsupportedOperationExceptionComplete(String message); } /** Callback class for {@link #getOtaStatus}. */ @@ -789,6 +794,18 @@ public class EuiccConnector extends StateMachine implements ServiceConnection { onCommandEnd(callback); }); } + + @Override + public void onUnsupportedOperationException( + String message) { + sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> { + ((GetAvailableMemoryInBytesCommandCallback) + callback) + .onUnsupportedOperationExceptionComplete( + message); + onCommandEnd(callback); + }); + } }); break; } diff --git a/src/java/com/android/internal/telephony/euicc/EuiccController.java b/src/java/com/android/internal/telephony/euicc/EuiccController.java index 6dd6f657c8..18b4b149d7 100644 --- a/src/java/com/android/internal/telephony/euicc/EuiccController.java +++ b/src/java/com/android/internal/telephony/euicc/EuiccController.java @@ -1331,8 +1331,13 @@ public class EuiccController extends IEuiccController.Stub { SubscriptionInfo subscriptionInfo = mSubscriptionManager.getActiveSubscriptionInfoForSimSlotIndex( slot.getPhoneIdFromPortIndex(portIndex)); - if (subscriptionInfo == null || subscriptionInfo.isOpportunistic()) { - // If the port is active and empty/opportunistic, return the portIndex. + if (subscriptionInfo == null + || subscriptionInfo.isOpportunistic() + || (mFeatureFlags.esimBootstrapProvisioningFlag() + && subscriptionInfo.getProfileClass() + == SubscriptionManager.PROFILE_CLASS_PROVISIONING)) { + // If the port is active and has empty/opportunistic/provisioning + // profiles then return the portIndex. return portIndex; } } @@ -1888,10 +1893,12 @@ public class EuiccController extends IEuiccController.Stub { return awaitResult(latch, eidRef); } - private long blockingGetAvailableMemoryInBytesFromEuiccService(int cardId) { + private long blockingGetAvailableMemoryInBytesFromEuiccService(int cardId) + throws UnsupportedOperationException { CountDownLatch latch = new CountDownLatch(1); AtomicReference<Long> memoryRef = new AtomicReference<>(EuiccManager.EUICC_MEMORY_FIELD_UNAVAILABLE); + AtomicReference<Exception> exceptionRef = new AtomicReference(); mConnector.getAvailableMemoryInBytes( cardId, new EuiccConnector.GetAvailableMemoryInBytesCommandCallback() { @@ -1902,11 +1909,24 @@ public class EuiccController extends IEuiccController.Stub { } @Override + public void onUnsupportedOperationExceptionComplete(String message) { + exceptionRef.set(new UnsupportedOperationException(message)); + latch.countDown(); + } + + @Override public void onEuiccServiceUnavailable() { latch.countDown(); } }); - return awaitResult(latch, memoryRef); + try { + return awaitResultOrException(latch, memoryRef, exceptionRef); + } catch (UnsupportedOperationException uoe) { + throw uoe; + } catch (Exception e) { + // Other type of exceptions are not expected here but re-throw in case that happens. + throw new UnsupportedOperationException(e); + } } private @OtaStatus int blockingGetOtaStatusFromEuiccService(int cardId) { @@ -1956,6 +1976,24 @@ public class EuiccController extends IEuiccController.Stub { return resultRef.get(); } + private static <T> T awaitResultOrException( + CountDownLatch latch, + AtomicReference<T> resultRef, + AtomicReference<Exception> resultException) + throws Exception { + try { + latch.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + if (resultException.get() != null) { + throw resultException.get(); + } + + return resultRef.get(); + } + // Returns whether the caller has carrier privilege on the given subscription. private boolean checkCarrierPrivilegeInMetadata(DownloadableSubscription subscription, String callingPackage) { diff --git a/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java index dae808adb5..e5afbeb9a3 100644 --- a/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java +++ b/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java @@ -171,7 +171,7 @@ public final class GsmSMSDispatcher extends SMSDispatcher { if(mPhone.getServiceState().getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_NR) { tracker.onFailed(mContext, getNotInServiceError(ss), NO_ERROR_CODE); - notifySmsSentFailedToEmergencyStateTracker(tracker); + notifySmsSentFailedToEmergencyStateTracker(tracker, false); return; } } diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java index b5a052d518..dcb3b207af 100644 --- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java +++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java @@ -3580,14 +3580,13 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { ImsPhoneConnection conn = findConnection(imsCall); // Since onCallInitiating and onCallProgressing reset mPendingMO, // we can't depend on mPendingMO. - if ((reasonInfo.getCode() == ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL - || reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED - || reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED) - && conn != null) { + if (conn != null) { logi("onCallStartFailed eccCategory=" + eccCategory); - if (reasonInfo.getCode() == ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL - || reasonInfo.getExtraCode() - == ImsReasonInfo.EXTRA_CODE_CALL_RETRY_EMERGENCY) { + int reason = reasonInfo.getCode(); + int extraCode = reasonInfo.getExtraCode(); + if ((reason == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED + && extraCode == ImsReasonInfo.EXTRA_CODE_CALL_RETRY_EMERGENCY) + || (reason == ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL)) { conn.setNonDetectableEmergencyCallInfo(eccCategory); } conn.setImsReasonInfo(reasonInfo); diff --git a/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java b/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java index 387495e109..cd5b7d61c6 100644 --- a/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java +++ b/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java @@ -16,12 +16,27 @@ package com.android.internal.telephony.metrics; +import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_ATTEMPTED_BITMASK; +import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_SUCCEEDED_BITMASK; +import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_VALIDATION_RESULT; +import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS; +import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_PACKET_FAIL_RATE; +import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_DNS_CONSECUTIVE_TIMEOUTS; + +import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.annotation.Nullable; +import android.net.ConnectivityDiagnosticsManager; +import android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback; +import android.net.ConnectivityDiagnosticsManager.ConnectivityReport; +import android.net.ConnectivityDiagnosticsManager.DataStallReport; import android.net.NetworkCapabilities; +import android.net.NetworkRequest; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; +import android.os.PersistableBundle; +import android.os.SystemClock; import android.telephony.AccessNetworkConstants; import android.telephony.Annotation.NetworkType; import android.telephony.CellSignalStrength; @@ -30,6 +45,7 @@ import android.telephony.ServiceState; import android.telephony.TelephonyManager; import android.telephony.data.DataCallResponse; import android.telephony.data.DataCallResponse.LinkStatus; +import android.text.TextUtils; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneFactory; @@ -38,11 +54,14 @@ import com.android.internal.telephony.data.DataNetwork; import com.android.internal.telephony.data.DataNetworkController; import com.android.internal.telephony.data.DataNetworkController.DataNetworkControllerCallback; import com.android.internal.telephony.data.DataStallRecoveryManager; +import com.android.internal.telephony.flags.FeatureFlags; import com.android.internal.telephony.subscription.SubscriptionInfoInternal; import com.android.internal.telephony.subscription.SubscriptionManagerService; import com.android.telephony.Rlog; import java.util.Set; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; /** * Generates metrics related to data stall recovery events per phone ID for the pushed atom. @@ -57,18 +76,31 @@ public class DataStallRecoveryStats { private static final String TAG = "DSRS-"; + private static final int UNSET_DIAGNOSTIC_STATE = -1; + + private static final long REFRESH_DURATION_IN_MILLIS = TimeUnit.MINUTES.toMillis(3); + // Handler to upload metrics. private final @NonNull Handler mHandler; private final @NonNull String mTag; private final @NonNull Phone mPhone; + private final @NonNull TelephonyManager mTelephonyManager; + private final @NonNull FeatureFlags mFeatureFlags; + + // Flag to control the DSRS diagnostics + private final boolean mIsDsrsDiagnosticsEnabled; // The interface name of the internet network. private @Nullable String mIfaceName = null; /* Metrics and stats data variables */ + // Record metrics refresh time in milliseconds to decide whether to refresh data again + @ElapsedRealtimeLong + private long mMetricsReflashTime = 0L; private int mPhoneId = 0; private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID; + private int mConvertedMccMnc = -1; private int mSignalStrength = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN; private int mBand = 0; // The RAT used for data (including IWLAN). @@ -88,17 +120,33 @@ public class DataStallRecoveryStats { private int mLinkDownBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED; private int mLinkUpBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED; + // Connectivity diagnostics states + private int mNetworkProbesResult = UNSET_DIAGNOSTIC_STATE; + private int mNetworkProbesType = UNSET_DIAGNOSTIC_STATE; + private int mNetworkValidationResult = UNSET_DIAGNOSTIC_STATE; + private int mTcpMetricsCollectionPeriodMillis = UNSET_DIAGNOSTIC_STATE; + private int mTcpPacketFailRate = UNSET_DIAGNOSTIC_STATE; + private int mDnsConsecutiveTimeouts = UNSET_DIAGNOSTIC_STATE; + + private ConnectivityDiagnosticsManager mConnectivityDiagnosticsManager = null; + private ConnectivityDiagnosticsCallback mConnectivityDiagnosticsCallback = null; + private static final Executor INLINE_EXECUTOR = x -> x.run(); + /** * Constructs a new instance of {@link DataStallRecoveryStats}. */ - public DataStallRecoveryStats(@NonNull final Phone phone, + public DataStallRecoveryStats( + @NonNull final Phone phone, + @NonNull FeatureFlags featureFlags, @NonNull final DataNetworkController dataNetworkController) { mTag = TAG + phone.getPhoneId(); mPhone = phone; + mFeatureFlags = featureFlags; HandlerThread handlerThread = new HandlerThread(mTag + "-thread"); handlerThread.start(); mHandler = new Handler(handlerThread.getLooper()); + mTelephonyManager = mPhone.getContext().getSystemService(TelephonyManager.class); dataNetworkController.registerDataNetworkControllerCallback( new DataNetworkControllerCallback(mHandler::post) { @@ -117,6 +165,45 @@ public class DataStallRecoveryStats { mInternetLinkStatus = status; } }); + + mIsDsrsDiagnosticsEnabled = mFeatureFlags.dsrsDiagnosticsEnabled(); + if (mIsDsrsDiagnosticsEnabled) { + try { + // Register ConnectivityDiagnosticsCallback to get diagnostics states + mConnectivityDiagnosticsManager = + mPhone.getContext().getSystemService(ConnectivityDiagnosticsManager.class); + mConnectivityDiagnosticsCallback = new ConnectivityDiagnosticsCallback() { + @Override + public void onConnectivityReportAvailable(@NonNull ConnectivityReport report) { + PersistableBundle bundle = report.getAdditionalInfo(); + mNetworkProbesResult = bundle.getInt(KEY_NETWORK_PROBES_SUCCEEDED_BITMASK); + mNetworkProbesType = bundle.getInt(KEY_NETWORK_PROBES_ATTEMPTED_BITMASK); + mNetworkValidationResult = bundle.getInt(KEY_NETWORK_VALIDATION_RESULT); + } + + @Override + public void onDataStallSuspected(@NonNull DataStallReport report) { + PersistableBundle bundle = report.getStallDetails(); + mTcpMetricsCollectionPeriodMillis = + bundle.getInt(KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS); + mTcpPacketFailRate = bundle.getInt(KEY_TCP_PACKET_FAIL_RATE); + mDnsConsecutiveTimeouts = bundle.getInt(KEY_DNS_CONSECUTIVE_TIMEOUTS); + } + }; + mConnectivityDiagnosticsManager.registerConnectivityDiagnosticsCallback( + new NetworkRequest.Builder() + .clearCapabilities() + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .build(), + INLINE_EXECUTOR, + mConnectivityDiagnosticsCallback + ); + } catch (Exception e) { + mConnectivityDiagnosticsManager = null; + mConnectivityDiagnosticsCallback = null; + } + } } /** @@ -194,10 +281,26 @@ public class DataStallRecoveryStats { */ private void refreshMetricsData() { logd("Refreshes the metrics data."); + // Update the metrics reflash time + mMetricsReflashTime = SystemClock.elapsedRealtime(); // Update phone id/carrier id and signal strength mPhoneId = mPhone.getPhoneId() + 1; mCarrierId = mPhone.getCarrierId(); mSignalStrength = mPhone.getSignalStrength().getLevel(); + if (mIsDsrsDiagnosticsEnabled) { + // Get the MCCMNC and convert it to an int + String networkOperator = mTelephonyManager.getNetworkOperator(); + if (!TextUtils.isEmpty(networkOperator)) { + try { + mConvertedMccMnc = Integer.parseInt(networkOperator); + } catch (NumberFormatException e) { + loge("Invalid MCCMNC format: " + networkOperator); + mConvertedMccMnc = -1; + } + } else { + mConvertedMccMnc = -1; + } + } // Update the bandwidth. updateBandwidths(); @@ -308,7 +411,8 @@ public class DataStallRecoveryStats { * @param isRecovered Whether the data stall has been recovered. * @param duration The duration from data stall occurred in milliseconds. * @param reason The reason for the recovery. - * @param isFirstValidation Whether this is the first validation after recovery. + * @param validationCount The total number of validation duration a data stall. + * @param actionValidationCount The number of validation for current action during a data stall * @param durationOfAction The duration of the current action in milliseconds. */ public Bundle getDataStallRecoveryMetricsData( @@ -316,28 +420,75 @@ public class DataStallRecoveryStats { boolean isRecovered, int duration, @DataStallRecoveryManager.RecoveredReason int reason, - boolean isFirstValidation, + int validationCount, + int actionValidationCount, int durationOfAction) { + + if (mIsDsrsDiagnosticsEnabled) { + // Refresh data if the data has not been updated within 3 minutes + final long refreshDuration = SystemClock.elapsedRealtime() - mMetricsReflashTime; + if (refreshDuration > REFRESH_DURATION_IN_MILLIS) { + // Refreshes the metrics data. + try { + refreshMetricsData(); + } catch (Exception e) { + loge("The metrics data cannot be refreshed.", e); + } + } + } + Bundle bundle = new Bundle(); - bundle.putInt("Action", action); - bundle.putBoolean("IsRecovered", isRecovered); - bundle.putInt("Duration", duration); - bundle.putInt("Reason", reason); - bundle.putBoolean("IsFirstValidation", isFirstValidation); - bundle.putInt("DurationOfAction", durationOfAction); - bundle.putInt("PhoneId", mPhoneId); - bundle.putInt("CarrierId", mCarrierId); - bundle.putInt("SignalStrength", mSignalStrength); - bundle.putInt("Band", mBand); - bundle.putInt("Rat", mRat); - bundle.putBoolean("IsOpportunistic", mIsOpportunistic); - bundle.putBoolean("IsMultiSim", mIsMultiSim); - bundle.putInt("NetworkRegState", mNetworkRegState); - bundle.putInt("OtherSignalStrength", mOtherSignalStrength); - bundle.putInt("OtherNetworkRegState", mOtherNetworkRegState); - bundle.putInt("InternetLinkStatus", mInternetLinkStatus); - bundle.putInt("LinkDownBandwidthKbps", mLinkDownBandwidthKbps); - bundle.putInt("LinkUpBandwidthKbps", mLinkUpBandwidthKbps); + + if (mIsDsrsDiagnosticsEnabled) { + bundle.putInt("Action", action); + bundle.putInt("IsRecovered", isRecovered ? 1 : 0); + bundle.putInt("Duration", duration); + bundle.putInt("Reason", reason); + bundle.putInt("DurationOfAction", durationOfAction); + bundle.putInt("ValidationCount", validationCount); + bundle.putInt("ActionValidationCount", actionValidationCount); + bundle.putInt("PhoneId", mPhoneId); + bundle.putInt("CarrierId", mCarrierId); + bundle.putInt("MccMnc", mConvertedMccMnc); + bundle.putInt("SignalStrength", mSignalStrength); + bundle.putInt("Band", mBand); + bundle.putInt("Rat", mRat); + bundle.putInt("IsOpportunistic", mIsOpportunistic ? 1 : 0); + bundle.putInt("IsMultiSim", mIsMultiSim ? 1 : 0); + bundle.putInt("NetworkRegState", mNetworkRegState); + bundle.putInt("OtherSignalStrength", mOtherSignalStrength); + bundle.putInt("OtherNetworkRegState", mOtherNetworkRegState); + bundle.putInt("InternetLinkStatus", mInternetLinkStatus); + bundle.putInt("LinkDownBandwidthKbps", mLinkDownBandwidthKbps); + bundle.putInt("LinkUpBandwidthKbps", mLinkUpBandwidthKbps); + bundle.putInt("NetworkProbesResult", mNetworkProbesResult); + bundle.putInt("NetworkProbesType", mNetworkProbesType); + bundle.putInt("NetworkValidationResult", mNetworkValidationResult); + bundle.putInt("TcpMetricsCollectionPeriodMillis", mTcpMetricsCollectionPeriodMillis); + bundle.putInt("TcpPacketFailRate", mTcpPacketFailRate); + bundle.putInt("DnsConsecutiveTimeouts", mDnsConsecutiveTimeouts); + } else { + bundle.putInt("Action", action); + bundle.putBoolean("IsRecovered", isRecovered); + bundle.putInt("Duration", duration); + bundle.putInt("Reason", reason); + bundle.putBoolean("IsFirstValidation", validationCount == 1); + bundle.putInt("DurationOfAction", durationOfAction); + bundle.putInt("PhoneId", mPhoneId); + bundle.putInt("CarrierId", mCarrierId); + bundle.putInt("SignalStrength", mSignalStrength); + bundle.putInt("Band", mBand); + bundle.putInt("Rat", mRat); + bundle.putBoolean("IsOpportunistic", mIsOpportunistic); + bundle.putBoolean("IsMultiSim", mIsMultiSim); + bundle.putInt("NetworkRegState", mNetworkRegState); + bundle.putInt("OtherSignalStrength", mOtherSignalStrength); + bundle.putInt("OtherNetworkRegState", mOtherNetworkRegState); + bundle.putInt("InternetLinkStatus", mInternetLinkStatus); + bundle.putInt("LinkDownBandwidthKbps", mLinkDownBandwidthKbps); + bundle.putInt("LinkUpBandwidthKbps", mLinkUpBandwidthKbps); + } + return bundle; } diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteConfig.java b/src/java/com/android/internal/telephony/satellite/SatelliteConfig.java new file mode 100644 index 0000000000..8d7e723184 --- /dev/null +++ b/src/java/com/android/internal/telephony/satellite/SatelliteConfig.java @@ -0,0 +1,233 @@ +/* + * 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.satellite; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.satellite.nano.SatelliteConfigData; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * SatelliteConfig is utility class for satellite. + * It is obtained through the getConfig() at the SatelliteConfigParser. + */ +public class SatelliteConfig { + + private static final String TAG = "SatelliteConfig"; + private static final String SATELLITE_DIR_NAME = "satellite"; + private static final String S2_CELL_FILE_NAME = "s2_cell_file"; + private int mVersion; + private Map<Integer, Map<String, Set<Integer>>> mSupportedServicesPerCarrier; + private List<String> mSatelliteRegionCountryCodes; + private Boolean mIsSatelliteRegionAllowed; + private Path mSatS2FilePath; + private SatelliteConfigData.SatelliteConfigProto mConfigData; + + public SatelliteConfig(SatelliteConfigData.SatelliteConfigProto configData) { + mConfigData = configData; + mVersion = mConfigData.version; + mSupportedServicesPerCarrier = getCarrierSupportedSatelliteServices(); + mSatelliteRegionCountryCodes = List.of( + mConfigData.deviceSatelliteRegion.countryCodes); + mIsSatelliteRegionAllowed = mConfigData.deviceSatelliteRegion.isAllowed; + mSatS2FilePath = null; + + Log.d(TAG, "mVersion:" + mVersion + " | " + + "mSupportedServicesPerCarrier:" + mSupportedServicesPerCarrier + " | " + + "mSatelliteRegionCountryCodes:" + mSatelliteRegionCountryCodes + " | " + + "mIsSatelliteRegionAllowed:" + mIsSatelliteRegionAllowed + " | " + + "s2CellFile size:" + mConfigData.deviceSatelliteRegion.s2CellFile.length); + } + + /** + * @return a Map data with carrier_id, plmns and allowed_services. + */ + private Map<Integer, Map<String, Set<Integer>>> getCarrierSupportedSatelliteServices() { + SatelliteConfigData.CarrierSupportedSatelliteServicesProto[] satelliteServices = + mConfigData.carrierSupportedSatelliteServices; + Map<Integer, Map<String, Set<Integer>>> carrierToServicesMap = new HashMap<>(); + for (SatelliteConfigData.CarrierSupportedSatelliteServicesProto carrierProto : + satelliteServices) { + SatelliteConfigData.SatelliteProviderCapabilityProto[] satelliteCapabilities = + carrierProto.supportedSatelliteProviderCapabilities; + Map<String, Set<Integer>> satelliteCapabilityMap = new HashMap<>(); + for (SatelliteConfigData.SatelliteProviderCapabilityProto capabilityProto : + satelliteCapabilities) { + String carrierPlmn = capabilityProto.carrierPlmn; + Set<Integer> allowedServices = new HashSet<>(); + for (int service : capabilityProto.allowedServices) { + allowedServices.add(service); + } + satelliteCapabilityMap.put(carrierPlmn, allowedServices); + } + carrierToServicesMap.put(carrierProto.carrierId, satelliteCapabilityMap); + } + return carrierToServicesMap; + } + + /** + * Get satellite plmns for carrier + * + * @param carrierId the carrier identifier. + * @return Plmns corresponding to carrier identifier. + */ + @NonNull + public List<String> getAllSatellitePlmnsForCarrier(int carrierId) { + if (mSupportedServicesPerCarrier != null) { + Map<String, Set<Integer>> satelliteCapabilitiesMap = mSupportedServicesPerCarrier.get( + carrierId); + if (satelliteCapabilitiesMap != null) { + return new ArrayList<>(satelliteCapabilitiesMap.keySet()); + } + } + Log.d(TAG, "getAllSatellitePlmnsForCarrier : mConfigData is null or no config data"); + return new ArrayList<>(); + } + + /** + * Get supported satellite services of all providers for a carrier. + * The format of the return value - Key: PLMN, Value: Set of supported satellite services. + * + * @param carrierId the carrier identifier. + * @return all supported satellite services for a carrier + */ + @NonNull + public Map<String, Set<Integer>> getSupportedSatelliteServices(int carrierId) { + if (mSupportedServicesPerCarrier != null) { + Map<String, Set<Integer>> satelliteCapaMap = + mSupportedServicesPerCarrier.get(carrierId); + if (satelliteCapaMap != null) { + return satelliteCapaMap; + } else { + Log.d(TAG, "No supported services found for carrier=" + carrierId); + } + } else { + Log.d(TAG, "mSupportedServicesPerCarrier is null"); + } + return new HashMap<>(); + } + + /** + * @return satellite region country codes + */ + @NonNull + public List<String> getDeviceSatelliteCountryCodes() { + if (mSatelliteRegionCountryCodes != null) { + return mSatelliteRegionCountryCodes; + } + Log.d(TAG, "getDeviceSatelliteCountryCodes : mConfigData is null or no config data"); + return new ArrayList<>(); + } + + /** + * @return satellite access allow value, if there is no config data then it returns null. + */ + @Nullable + public Boolean isSatelliteDataForAllowedRegion() { + if (mIsSatelliteRegionAllowed == null) { + Log.d(TAG, "getIsSatelliteRegionAllowed : mConfigData is null or no config data"); + } + return mIsSatelliteRegionAllowed; + } + + + /** + * @param context the Context + * @return satellite s2_cell_file path + */ + @Nullable + public Path getSatelliteS2CellFile(@Nullable Context context) { + if (context == null) { + Log.d(TAG, "getSatelliteS2CellFile : context is null"); + return null; + } + + if (isFileExist(mSatS2FilePath)) { + Log.d(TAG, "File mSatS2FilePath is already exist"); + return mSatS2FilePath; + } + + if (mConfigData != null && mConfigData.deviceSatelliteRegion != null) { + mSatS2FilePath = copySatS2FileToPhoneDirectory(context, + mConfigData.deviceSatelliteRegion.s2CellFile); + return mSatS2FilePath; + } + Log.d(TAG, "getSatelliteS2CellFile :" + + "mConfigData is null or mConfigData.deviceSatelliteRegion is null"); + return null; + } + + /** + * @param context the Context + * @param byteArrayFile byte array type of protobuffer config data + * @return the satellite_cell_file path + */ + @Nullable + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public Path copySatS2FileToPhoneDirectory(@Nullable Context context, + @Nullable byte[] byteArrayFile) { + + if (context == null || byteArrayFile == null) { + Log.d(TAG, "copySatS2FileToPhoneDirectory : context or byteArrayFile are null"); + return null; + } + + File satS2FileDir = context.getDir(SATELLITE_DIR_NAME, Context.MODE_PRIVATE); + if (!satS2FileDir.exists()) { + satS2FileDir.mkdirs(); + } + + Path targetSatS2FilePath = satS2FileDir.toPath().resolve(S2_CELL_FILE_NAME); + try { + InputStream inputStream = new ByteArrayInputStream(byteArrayFile); + if (inputStream == null) { + Log.d(TAG, "copySatS2FileToPhoneDirectory: Resource=" + S2_CELL_FILE_NAME + + " not found"); + } else { + Files.copy(inputStream, targetSatS2FilePath, StandardCopyOption.REPLACE_EXISTING); + } + } catch (IOException ex) { + Log.e(TAG, "copySatS2FileToPhoneDirectory: ex=" + ex); + } + return targetSatS2FilePath; + } + + /** + * @return {@code true} if the SatS2File is already existed and {@code false} otherwise. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public boolean isFileExist(Path filePath) { + return Files.exists(filePath); + } +} diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteConfigParser.java b/src/java/com/android/internal/telephony/satellite/SatelliteConfigParser.java new file mode 100644 index 0000000000..4ff1880ba5 --- /dev/null +++ b/src/java/com/android/internal/telephony/satellite/SatelliteConfigParser.java @@ -0,0 +1,94 @@ +/* + * 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.satellite; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.Log; + +import com.android.internal.telephony.configupdate.ConfigParser; +import com.android.internal.telephony.satellite.nano.SatelliteConfigData; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + + +/** + * SatelliteConfigParser parses the config data and create SatelliteConfig. + * The config data is located at "/data/misc/telephonyconfig/telephony_config.pb". + * It is obtained through the getConfigParser() at the TelephonyConfigUpdateInstallReceiver. + */ +public class SatelliteConfigParser extends ConfigParser<SatelliteConfig> { + private static final String TAG = "SatelliteConfigParser"; + + /** + * Create an instance of SatelliteConfigParser with byte array data. + * + * @param data the config data formatted as byte array. + */ + public SatelliteConfigParser(@Nullable byte[] data) { + super(data); + } + + /** + * Create an instance of SatelliteConfigParser with InputStream data. + * + * @param input the config data formatted as InputStream. + */ + public SatelliteConfigParser(@NonNull InputStream input) + throws IOException { + super(input); + } + + /** + * Create an instance of SatelliteConfigParser with File data. + * + * @param file the config data formatted as File. + */ + public SatelliteConfigParser(@NonNull File file) throws IOException { + super(file); + } + + @Override + protected void parseData(@Nullable byte[] data) { + boolean parseError = false; + try { + if (data == null) { + Log.d(TAG, "config data is null"); + return; + } + SatelliteConfigData.TelephonyConfigProto telephonyConfigData = + SatelliteConfigData.TelephonyConfigProto.parseFrom(data); + if (telephonyConfigData == null || telephonyConfigData.satellite == null) { + Log.e(TAG, "telephonyConfigData or telephonyConfigData.satellite is null"); + return; + } + mVersion = telephonyConfigData.satellite.version; + mConfig = new SatelliteConfig(telephonyConfigData.satellite); + Log.d(TAG, "SatelliteConfig is created"); + } catch (Exception e) { + parseError = true; + Log.e(TAG, "Parse Error : " + e.getMessage()); + } finally { + if (parseError) { + mVersion = VERSION_UNKNOWN; + mConfig = null; + } + } + } +} diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteController.java b/src/java/com/android/internal/telephony/satellite/SatelliteController.java index 0d3973d127..2c12939380 100644 --- a/src/java/com/android/internal/telephony/satellite/SatelliteController.java +++ b/src/java/com/android/internal/telephony/satellite/SatelliteController.java @@ -34,6 +34,8 @@ import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODE import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED; import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS; +import static com.android.internal.telephony.configupdate.ConfigProviderAdaptor.DOMAIN_SATELLITE; + import android.annotation.ArrayRes; import android.annotation.NonNull; import android.annotation.Nullable; @@ -66,6 +68,8 @@ import android.os.ICancellationSignal; import android.os.Looper; import android.os.Message; import android.os.PersistableBundle; +import android.os.Registrant; +import android.os.RegistrantList; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceSpecificException; @@ -89,6 +93,7 @@ import android.telephony.satellite.SatelliteCapabilities; import android.telephony.satellite.SatelliteDatagram; import android.telephony.satellite.SatelliteManager; import android.util.Log; +import android.util.Pair; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.uwb.UwbManager; @@ -101,6 +106,9 @@ import com.android.internal.telephony.DeviceStateMonitor; import com.android.internal.telephony.IIntegerConsumer; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneFactory; +import com.android.internal.telephony.configupdate.ConfigParser; +import com.android.internal.telephony.configupdate.ConfigProviderAdaptor; +import com.android.internal.telephony.configupdate.TelephonyConfigUpdateInstallReceiver; import com.android.internal.telephony.flags.FeatureFlags; import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats; import com.android.internal.telephony.satellite.metrics.ProvisionMetricsStats; @@ -118,6 +126,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; @@ -187,6 +196,7 @@ public class SatelliteController extends Handler { 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; + private static final int EVENT_SATELLITE_CONFIG_DATA_UPDATED = 40; @NonNull private static SatelliteController sInstance; @NonNull private final Context mContext; @@ -298,6 +308,7 @@ public class SatelliteController extends Handler { @NonNull private final CarrierConfigManager mCarrierConfigManager; @NonNull private final CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener; + @NonNull private final ConfigProviderAdaptor.Callback mConfigDataUpdatedCallback; @NonNull private final Object mCarrierConfigArrayLock = new Object(); @GuardedBy("mCarrierConfigArrayLock") @NonNull private final SparseArray<PersistableBundle> mCarrierConfigArray = new SparseArray<>(); @@ -365,6 +376,8 @@ public class SatelliteController extends Handler { private static final String NOTIFICATION_CHANNEL = "satelliteChannel"; private static final String NOTIFICATION_CHANNEL_ID = "satellite"; + private final RegistrantList mSatelliteConfigUpdateChangedRegistrants = new RegistrantList(); + /** * @return The singleton instance of SatelliteController. */ @@ -454,12 +467,64 @@ public class SatelliteController extends Handler { handleCarrierConfigChanged(slotIndex, subId, carrierId, specificCarrierId); mCarrierConfigManager.registerCarrierConfigChangeListener( new HandlerExecutor(new Handler(looper)), mCarrierConfigChangeListener); + + mConfigDataUpdatedCallback = new ConfigProviderAdaptor.Callback() { + @Override + public void onChanged(@Nullable ConfigParser config) { + SatelliteControllerHandlerRequest request = + new SatelliteControllerHandlerRequest(true, + SatelliteServiceUtils.getPhone()); + sendRequestAsync(EVENT_SATELLITE_CONFIG_DATA_UPDATED, request, null); + } + }; + TelephonyConfigUpdateInstallReceiver.getInstance() + .registerCallback(Executors.newSingleThreadExecutor(), mConfigDataUpdatedCallback); + mDSM.registerForSignalStrengthReportDecision(this, CMD_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING, null); loadSatelliteSharedPreferences(); mWaitTimeForSatelliteEnablingResponse = getWaitForSatelliteEnablingResponseTimeoutMillis(); } + /** + * Register a callback to get a updated satellite config data. + * @param h Handler to notify + * @param what msg.what when the message is delivered + * @param obj AsyncResult.userObj when the message is delivered + */ + public void registerForConfigUpdateChanged(Handler h, int what, Object obj) { + Registrant r = new Registrant(h, what, obj); + mSatelliteConfigUpdateChangedRegistrants.add(r); + } + + /** + * Unregister a callback to get a updated satellite config data. + * @param h Handler to notify + */ + public void unregisterForConfigUpdateChanged(Handler h) { + mSatelliteConfigUpdateChangedRegistrants.remove(h); + } + + /** + * Get satelliteConfig from SatelliteConfigParser + */ + public SatelliteConfig getSatelliteConfig() { + if (getSatelliteConfigParser() == null) { + Log.d(TAG, "getSatelliteConfigParser() is not ready"); + return null; + } + return (SatelliteConfig) getSatelliteConfigParser().getConfig(); + } + + /** + * Get SatelliteConfigParser from TelephonyConfigUpdateInstallReceiver + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public SatelliteConfigParser getSatelliteConfigParser() { + return (SatelliteConfigParser) TelephonyConfigUpdateInstallReceiver + .getInstance().getConfigParser(DOMAIN_SATELLITE); + } + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) protected void initializeSatelliteModeRadios() { if (mContentResolver != null) { @@ -1287,6 +1352,12 @@ public class SatelliteController extends Handler { break; } + case EVENT_SATELLITE_CONFIG_DATA_UPDATED: { + handleEventConfigDataUpdated(); + mSatelliteConfigUpdateChangedRegistrants.notifyRegistrants(); + break; + } + default: Log.w(TAG, "SatelliteControllerHandler: unexpected message code: " + msg.what); @@ -1294,6 +1365,19 @@ public class SatelliteController extends Handler { } } + private void handleEventConfigDataUpdated() { + updateSupportedSatelliteServicesForActiveSubscriptions(); + int[] activeSubIds = mSubscriptionManagerService.getActiveSubIdList(true); + if (activeSubIds != null) { + for (int subId : activeSubIds) { + processNewCarrierConfigData(subId); + } + } else { + loge("updateSupportedSatelliteServicesForActiveSubscriptions: " + + "activeSubIds is null"); + } + } + private void notifyRequester(SatelliteControllerHandlerRequest request) { synchronized (request) { request.notifyAll(); @@ -2513,21 +2597,21 @@ public class SatelliteController extends Handler { } /** - * @return {@code true} if any subscription on the device is connected to satellite, - * {@code false} otherwise. + * @return {@code Pair<true, subscription ID>} if any subscription on the device is connected to + * satellite, {@code Pair<false, null>} otherwise. */ - private boolean isUsingNonTerrestrialNetworkViaCarrier() { + private Pair<Boolean, Integer> isUsingNonTerrestrialNetworkViaCarrier() { if (!mFeatureFlags.carrierEnabledSatelliteFlag()) { logd("isUsingNonTerrestrialNetwork: carrierEnabledSatelliteFlag is disabled"); - return false; + return new Pair<>(false, null); } for (Phone phone : PhoneFactory.getPhones()) { ServiceState serviceState = phone.getServiceState(); if (serviceState != null && serviceState.isUsingNonTerrestrialNetwork()) { - return true; + return new Pair<>(true, phone.getSubId()); } } - return false; + return new Pair<>(false, null); } /** @@ -2541,7 +2625,7 @@ public class SatelliteController extends Handler { + " is disabled"); return false; } - if (isUsingNonTerrestrialNetworkViaCarrier()) { + if (isUsingNonTerrestrialNetworkViaCarrier().first) { return true; } for (Phone phone : PhoneFactory.getPhones()) { @@ -2789,6 +2873,7 @@ public class SatelliteController extends Handler { * @return true if satellite is provisioned on the given subscription else return false. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + @Nullable protected Boolean isSatelliteViaOemProvisioned() { synchronized (mSatelliteViaOemProvisionLock) { if (mOverriddenIsSatelliteViaOemProvisioned != null) { @@ -3193,9 +3278,22 @@ public class SatelliteController extends Handler { return; } + SatelliteConfig satelliteConfig = getSatelliteConfig(); + if (satelliteConfig != null) { + TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); + int carrierId = tm.createForSubscriptionId(subId).getSimCarrierId(); + List<String> plmnList = satelliteConfig.getAllSatellitePlmnsForCarrier(carrierId); + if (!plmnList.isEmpty()) { + logd("mMergedPlmnListPerCarrier is updated by ConfigUpdater : " + plmnList); + mMergedPlmnListPerCarrier.put(subId, plmnList); + return; + } + } + if (mSatelliteServicesSupportedByCarriers.containsKey(subId)) { carrierPlmnList = mSatelliteServicesSupportedByCarriers.get(subId).keySet().stream().toList(); + logd("mMergedPlmnListPerCarrier is updated by carrier config"); } else { carrierPlmnList = new ArrayList<>(); } @@ -3205,10 +3303,31 @@ public class SatelliteController extends Handler { } private void updateSupportedSatelliteServices(int subId) { + logd("updateSupportedSatelliteServices with subId " + subId); synchronized (mSupportedSatelliteServicesLock) { + SatelliteConfig satelliteConfig = getSatelliteConfig(); + + TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); + int carrierId = tm.createForSubscriptionId(subId).getSimCarrierId(); + + if (satelliteConfig != null) { + Map<String, Set<Integer>> supportedServicesPerPlmn = + satelliteConfig.getSupportedSatelliteServices(carrierId); + if (!supportedServicesPerPlmn.isEmpty()) { + mSatelliteServicesSupportedByCarriers.put(subId, supportedServicesPerPlmn); + logd("updateSupportedSatelliteServices using ConfigUpdater, " + + "supportedServicesPerPlmn = " + supportedServicesPerPlmn); + updatePlmnListPerCarrier(subId); + return; + } else { + logd("supportedServicesPerPlmn is empty"); + } + } + mSatelliteServicesSupportedByCarriers.put( subId, readSupportedSatelliteServicesFromCarrierConfig(subId)); updatePlmnListPerCarrier(subId); + logd("updateSupportedSatelliteServices using carrier config"); } } @@ -3262,8 +3381,11 @@ public class SatelliteController extends Handler { updateCarrierConfig(subId); updateEntitlementPlmnListPerCarrier(subId); updateSupportedSatelliteServicesForActiveSubscriptions(); - configureSatellitePlmnForCarrier(subId); + processNewCarrierConfigData(subId); + } + private void processNewCarrierConfigData(int subId) { + configureSatellitePlmnForCarrier(subId); synchronized (mIsSatelliteEnabledLock) { mSatelliteAttachRestrictionForCarrierArray.clear(); mIsSatelliteAttachEnabledForCarrierArrayPerSub.clear(); @@ -3875,7 +3997,13 @@ public class SatelliteController extends Handler { } private void determineSystemNotification() { - if (isUsingNonTerrestrialNetworkViaCarrier()) { + if (!mFeatureFlags.carrierEnabledSatelliteFlag()) { + logd("determineSystemNotification: carrierEnabledSatelliteFlag is disabled"); + return; + } + + Pair<Boolean, Integer> isNtn = isUsingNonTerrestrialNetworkViaCarrier(); + if (isNtn.first) { if (mSharedPreferences == null) { try { mSharedPreferences = mContext.getSharedPreferences(SATELLITE_SHARED_PREF, @@ -3885,18 +4013,18 @@ public class SatelliteController extends Handler { } } if (mSharedPreferences == null) { - loge("handleEventServiceStateChanged: Cannot get default shared preferences"); + loge("determineSystemNotification: Cannot get default shared preferences"); return; } if (!mSharedPreferences.getBoolean(SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY, false)) { - showSatelliteSystemNotification(); + showSatelliteSystemNotification(isNtn.second); mSharedPreferences.edit().putBoolean(SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY, true).apply(); } } } - private void showSatelliteSystemNotification() { + private void showSatelliteSystemNotification(int subId) { logd("showSatelliteSystemNotification"); final NotificationChannel notificationChannel = new NotificationChannel( NOTIFICATION_CHANNEL_ID, @@ -3934,6 +4062,7 @@ public class SatelliteController extends Handler { // Add action to invoke Satellite setting activity in Settings. Intent intentSatelliteSetting = new Intent(ACTION_SATELLITE_SETTING); + intentSatelliteSetting.putExtra("sub_id", subId); PendingIntent pendingIntentSatelliteSetting = PendingIntent.getActivity(mContext, 0, intentSatelliteSetting, PendingIntent.FLAG_IMMUTABLE); diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java b/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java index 149b0543a5..c491476534 100644 --- a/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java +++ b/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java @@ -309,8 +309,14 @@ public class SatelliteSOSMessageRecommender extends Handler { } } - private boolean isSatelliteViaOemAvailable() { - return mSatelliteController.isSatelliteViaOemProvisioned(); + /** + * Check if satellite is available via OEM + * @return {@code true} if satellite is provisioned via OEM else return {@code false} + */ + @VisibleForTesting + public boolean isSatelliteViaOemAvailable() { + Boolean satelliteProvisioned = mSatelliteController.isSatelliteViaOemProvisioned(); + return satelliteProvisioned != null ? satelliteProvisioned : false; } private boolean isSatelliteViaCarrierAvailable() { diff --git a/src/java/com/android/internal/telephony/security/NullCipherNotifier.java b/src/java/com/android/internal/telephony/security/NullCipherNotifier.java index 0ab82992ba..3ece701874 100644 --- a/src/java/com/android/internal/telephony/security/NullCipherNotifier.java +++ b/src/java/com/android/internal/telephony/security/NullCipherNotifier.java @@ -16,10 +16,29 @@ package com.android.internal.telephony.security; +import static android.telephony.SecurityAlgorithmUpdate.SECURITY_ALGORITHM_UNKNOWN; + +import static com.android.internal.telephony.security.CellularNetworkSecuritySafetySource.NULL_CIPHER_STATE_ENCRYPTED; +import static com.android.internal.telephony.security.CellularNetworkSecuritySafetySource.NULL_CIPHER_STATE_NOTIFY_ENCRYPTED; +import static com.android.internal.telephony.security.CellularNetworkSecuritySafetySource.NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED; + +import android.annotation.IntDef; +import android.content.Context; import android.telephony.SecurityAlgorithmUpdate; +import android.telephony.SecurityAlgorithmUpdate.ConnectionEvent; +import android.telephony.SecurityAlgorithmUpdate.SecurityAlgorithm; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.telephony.Rlog; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.HashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ScheduledExecutorService; + /** * Encapsulates logic to emit notifications to the user that a null cipher is in use. A null cipher * is one that does not attempt to implement any encryption. @@ -27,44 +46,84 @@ import com.android.telephony.Rlog; * <p>This class will either emit notifications through SafetyCenterManager if SafetyCenter exists * on a device, or it will emit system notifications otherwise. * + * TODO(b/319662115): handle radio availability, no service, SIM removal, etc. + * * @hide */ public class NullCipherNotifier { - private static final String TAG = "NullCipherNotifier"; private static NullCipherNotifier sInstance; + private final CellularNetworkSecuritySafetySource mSafetySource; + private final HashMap<Integer, SubscriptionState> mSubscriptionState = new HashMap<>(); + + private final Object mEnabledLock = new Object(); + @GuardedBy("mEnabledLock") private boolean mEnabled = false; + // This is a single threaded executor. This is important because we want to ensure certain + // events are strictly serialized. + private ScheduledExecutorService mSerializedWorkQueue; + /** * Gets a singleton NullCipherNotifier. */ - public static synchronized NullCipherNotifier getInstance() { + public static synchronized NullCipherNotifier getInstance( + CellularNetworkSecuritySafetySource safetySource) { if (sInstance == null) { - sInstance = new NullCipherNotifier(); + sInstance = new NullCipherNotifier( + Executors.newSingleThreadScheduledExecutor(), + safetySource); } return sInstance; } - private NullCipherNotifier() {} + @VisibleForTesting + public NullCipherNotifier( + ScheduledExecutorService notificationQueue, + CellularNetworkSecuritySafetySource safetySource) { + mSerializedWorkQueue = notificationQueue; + mSafetySource = safetySource; + } /** * Adds a security algorithm update. If appropriate, this will trigger a user notification. */ - public void onSecurityAlgorithmUpdate(int phoneId, SecurityAlgorithmUpdate update) { - // TODO (b/315005938) this is a stub method for now. Logic - // for tracking disclosures and emitting notifications will flow - // from here. - Rlog.d(TAG, "Security algorithm update: phoneId = " + phoneId + " " + update); + public void onSecurityAlgorithmUpdate( + Context context, int subId, SecurityAlgorithmUpdate update) { + Rlog.d(TAG, "Security algorithm update: subId = " + subId + " " + update); + + if (shouldIgnoreUpdate(update)) { + return; + } + + try { + mSerializedWorkQueue.execute(() -> { + SubscriptionState subState = mSubscriptionState.get(subId); + if (subState == null) { + subState = new SubscriptionState(); + mSubscriptionState.put(subId, subState); + } + + @CellularNetworkSecuritySafetySource.NullCipherState int nullCipherState = + subState.update(update); + mSafetySource.setNullCipherState(context, subId, nullCipherState); + }); + } catch (RejectedExecutionException e) { + Rlog.e(TAG, "Failed to schedule onEnableNotifier: " + e.getMessage()); + } } /** * Enables null cipher notification; {@code onSecurityAlgorithmUpdate} will start handling * security algorithm updates and send notifications to the user when required. */ - public void enable() { - Rlog.d(TAG, "enabled"); - mEnabled = true; + public void enable(Context context) { + synchronized (mEnabledLock) { + Rlog.d(TAG, "enabled"); + mEnabled = true; + scheduleOnEnabled(context, true); + } } /** @@ -73,12 +132,180 @@ public class NullCipherNotifier { * {@code onSecurityAlgorithmUpdate} is called while in a disabled state, security algorithm * updates will be dropped. */ - public void disable() { - Rlog.d(TAG, "disabled"); - mEnabled = false; + public void disable(Context context) { + synchronized (mEnabledLock) { + Rlog.d(TAG, "disabled"); + mEnabled = false; + scheduleOnEnabled(context, false); + } } + /** Checks whether the null cipher notification is enabled. */ public boolean isEnabled() { - return mEnabled; + synchronized (mEnabledLock) { + return mEnabled; + } } + + private void scheduleOnEnabled(Context context, boolean enabled) { + try { + mSerializedWorkQueue.execute(() -> { + Rlog.i(TAG, "On enable notifier. Enable value: " + enabled); + mSafetySource.setNullCipherIssueEnabled(context, enabled); + }); + } catch (RejectedExecutionException e) { + Rlog.e(TAG, "Failed to schedule onEnableNotifier: " + e.getMessage()); + } + + } + + /** Returns whether the update should be dropped and the monitoring state left unchanged. */ + private static boolean shouldIgnoreUpdate(SecurityAlgorithmUpdate update) { + // Ignore emergencies. + if (update.isUnprotectedEmergency()) { + return true; + } + + switch (update.getConnectionEvent()) { + // Ignore non-link layer protocols. Monitoring is only looking for data exposed + // over-the-air so only the link layer protocols are tracked. Higher-level protocols can + // protect data further into the network but that is out of scope. + case SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_SIP: + case SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_RTP: + case SecurityAlgorithmUpdate.CONNECTION_EVENT_VONR_SIP: + case SecurityAlgorithmUpdate.CONNECTION_EVENT_VONR_RTP: + // Ignore emergencies. + case SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_SIP_SOS: + case SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_RTP_SOS: + case SecurityAlgorithmUpdate.CONNECTION_EVENT_VONR_SIP_SOS: + case SecurityAlgorithmUpdate.CONNECTION_EVENT_VONR_RTP_SOS: + return true; + default: + return false; + } + } + + /** + * Determines whether an algorithm does not attempt to implement any encryption and is therefore + * considered a null cipher. + * + * <p>Only the algorithms known to be null ciphers are classified as such. Explicitly unknown + * algorithms, or algorithms that are unknown by means of values added to newer HALs, are + * assumed not to be null ciphers. + */ + private static boolean isNullCipher(@SecurityAlgorithm int algorithm) { + switch (algorithm) { + case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_A50: + case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_GEA0: + case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_UEA0: + case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA0: + case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_NEA0: + case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_IMS_NULL: + case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_SIP_NULL: + case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_SRTP_NULL: + case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_OTHER: + return true; + default: + return false; + } + } + + /** The state of network connections for a subscription. */ + private static final class SubscriptionState { + private @NetworkClass int mActiveNetworkClass = NETWORK_CLASS_UNKNOWN; + private final HashMap<Integer, ConnectionState> mState = new HashMap<>(); + + private @CellularNetworkSecuritySafetySource.NullCipherState int + update(SecurityAlgorithmUpdate update) { + boolean fromNullCipherState = hasNullCipher(); + + @NetworkClass int networkClass = getNetworkClass(update.getConnectionEvent()); + if (networkClass != mActiveNetworkClass || networkClass == NETWORK_CLASS_UNKNOWN) { + mState.clear(); + mActiveNetworkClass = networkClass; + } + + ConnectionState fromState = + mState.getOrDefault(update.getConnectionEvent(), ConnectionState.UNKNOWN); + ConnectionState toState = new ConnectionState( + update.getEncryption() == SECURITY_ALGORITHM_UNKNOWN + ? fromState.getEncryption() + : update.getEncryption(), + update.getIntegrity() == SECURITY_ALGORITHM_UNKNOWN + ? fromState.getIntegrity() + : update.getIntegrity()); + mState.put(update.getConnectionEvent(), toState); + + if (hasNullCipher()) { + return NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED; + } + if (!fromNullCipherState || mActiveNetworkClass == NETWORK_CLASS_UNKNOWN) { + return NULL_CIPHER_STATE_ENCRYPTED; + } + return NULL_CIPHER_STATE_NOTIFY_ENCRYPTED; + } + + private boolean hasNullCipher() { + return mState.values().stream().anyMatch(ConnectionState::hasNullCipher); + } + + private static final int NETWORK_CLASS_UNKNOWN = 0; + private static final int NETWORK_CLASS_2G = 2; + private static final int NETWORK_CLASS_3G = 3; + private static final int NETWORK_CLASS_4G = 4; + private static final int NETWORK_CLASS_5G = 5; + + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"NETWORK_CLASS_"}, value = {NETWORK_CLASS_UNKNOWN, + NETWORK_CLASS_2G, NETWORK_CLASS_3G, NETWORK_CLASS_4G, + NETWORK_CLASS_5G}) + private @interface NetworkClass {} + + private static @NetworkClass int getNetworkClass( + @ConnectionEvent int connectionEvent) { + switch (connectionEvent) { + case SecurityAlgorithmUpdate.CONNECTION_EVENT_CS_SIGNALLING_GSM: + case SecurityAlgorithmUpdate.CONNECTION_EVENT_PS_SIGNALLING_GPRS: + return NETWORK_CLASS_2G; + case SecurityAlgorithmUpdate.CONNECTION_EVENT_CS_SIGNALLING_3G: + case SecurityAlgorithmUpdate.CONNECTION_EVENT_PS_SIGNALLING_3G: + return NETWORK_CLASS_3G; + case SecurityAlgorithmUpdate.CONNECTION_EVENT_NAS_SIGNALLING_LTE: + case SecurityAlgorithmUpdate.CONNECTION_EVENT_AS_SIGNALLING_LTE: + return NETWORK_CLASS_4G; + case SecurityAlgorithmUpdate.CONNECTION_EVENT_NAS_SIGNALLING_5G: + case SecurityAlgorithmUpdate.CONNECTION_EVENT_AS_SIGNALLING_5G: + return NETWORK_CLASS_5G; + default: + return NETWORK_CLASS_UNKNOWN; + } + } + } + + /** The state of security algorithms for a network connection. */ + private static final class ConnectionState { + private static final ConnectionState UNKNOWN = + new ConnectionState(SECURITY_ALGORITHM_UNKNOWN, SECURITY_ALGORITHM_UNKNOWN); + + private final @SecurityAlgorithm int mEncryption; + private final @SecurityAlgorithm int mIntegrity; + + private ConnectionState( + @SecurityAlgorithm int encryption, @SecurityAlgorithm int integrity) { + mEncryption = encryption; + mIntegrity = integrity; + } + + private @SecurityAlgorithm int getEncryption() { + return mEncryption; + } + + private @SecurityAlgorithm int getIntegrity() { + return mIntegrity; + } + + private boolean hasNullCipher() { + return isNullCipher(mEncryption) || isNullCipher(mIntegrity); + } + }; } diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java b/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java index a2ebf4ed42..82af4e80e4 100644 --- a/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java +++ b/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java @@ -641,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; } /** @@ -1260,7 +1261,7 @@ public class SubscriptionInfoInternal { .setCarrierName(mCarrierName) .setDisplayNameSource(mDisplayNameSource) .setIconTint(mIconTint) - .setNumber(mNumber) + .setNumber(getNumber()) .setDataRoaming(mDataRoaming) .setMcc(mMcc) .setMnc(mMnc) @@ -1308,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 |