diff options
-rw-r--r-- | flags/main.aconfig | 13 | ||||
-rw-r--r-- | src/com/google/android/iwlan/ErrorPolicyManager.java | 1 | ||||
-rw-r--r-- | src/com/google/android/iwlan/IwlanDataService.java | 164 | ||||
-rw-r--r-- | src/com/google/android/iwlan/IwlanEventListener.java | 51 | ||||
-rw-r--r-- | src/com/google/android/iwlan/epdg/EpdgSelector.java | 106 | ||||
-rw-r--r-- | src/com/google/android/iwlan/epdg/EpdgTunnelManager.java | 102 | ||||
-rw-r--r-- | src/com/google/android/iwlan/epdg/TunnelSetupRequest.java | 7 | ||||
-rw-r--r-- | test/com/google/android/iwlan/ErrorPolicyManagerTest.java | 59 | ||||
-rw-r--r-- | test/com/google/android/iwlan/IwlanDataServiceTest.java | 355 | ||||
-rw-r--r-- | test/com/google/android/iwlan/IwlanEventListenerTest.java | 72 | ||||
-rw-r--r-- | test/com/google/android/iwlan/epdg/EpdgSelectorTest.java | 178 | ||||
-rw-r--r-- | test/com/google/android/iwlan/epdg/EpdgTunnelManagerTest.java | 113 |
12 files changed, 1097 insertions, 124 deletions
diff --git a/flags/main.aconfig b/flags/main.aconfig index 6749d7f..57b0527 100644 --- a/flags/main.aconfig +++ b/flags/main.aconfig @@ -1,4 +1,5 @@ package: "com.google.android.iwlan.flags" +container: "system" flag { name: "prevent_epdg_selection_threads_exhausted" @@ -24,3 +25,15 @@ flag { description: "Reorder IKE and Child SA security transforms with high secured as prioritized" bug: "306323917" } +flag { + name: "epdg_selection_exclude_failed_ip_address" + namespace: "iwlan_telephony" + description: "Exclude the failed ip address in epdg selection until tunnel establish successfully or all ip address candidates are failed" + bug: "300026897" +} +flag { + name: "update_n1_mode_on_ui_change" + namespace: "iwlan_telephony" + description: "Enables a 5G SA capable UE to re-establish PDN connections over Wi-Fi with/without N1_MODE_CAPABILITY, when the user enables/disables 5G via UI/UX." + bug: "273384888" +} diff --git a/src/com/google/android/iwlan/ErrorPolicyManager.java b/src/com/google/android/iwlan/ErrorPolicyManager.java index bef25ec..290283a 100644 --- a/src/com/google/android/iwlan/ErrorPolicyManager.java +++ b/src/com/google/android/iwlan/ErrorPolicyManager.java @@ -1372,6 +1372,7 @@ public class ErrorPolicyManager { case IwlanEventListener.APM_DISABLE_EVENT: case IwlanEventListener.WIFI_DISABLE_EVENT: case IwlanEventListener.WIFI_CALLING_DISABLE_EVENT: + case IwlanEventListener.WIFI_AP_CHANGED_EVENT: unthrottleLastErrorOnEvent(msg.what); break; default: diff --git a/src/com/google/android/iwlan/IwlanDataService.java b/src/com/google/android/iwlan/IwlanDataService.java index 58d000c..cd43ece 100644 --- a/src/com/google/android/iwlan/IwlanDataService.java +++ b/src/com/google/android/iwlan/IwlanDataService.java @@ -18,6 +18,7 @@ package com.google.android.iwlan; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.net.ipsec.ike.ike3gpp.Ike3gppParams.PDU_SESSION_ID_UNSET; import android.content.Context; import android.content.Intent; @@ -66,6 +67,8 @@ import com.google.android.iwlan.epdg.EpdgSelector; import com.google.android.iwlan.epdg.EpdgTunnelManager; import com.google.android.iwlan.epdg.TunnelLinkProperties; import com.google.android.iwlan.epdg.TunnelSetupRequest; +import com.google.android.iwlan.flags.FeatureFlags; +import com.google.android.iwlan.flags.FeatureFlagsImpl; import com.google.android.iwlan.proto.MetricsAtom; import java.io.FileDescriptor; @@ -75,6 +78,7 @@ import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.HashMap; @@ -86,6 +90,7 @@ import java.util.concurrent.ConcurrentHashMap; public class IwlanDataService extends DataService { + private final FeatureFlags mFeatureFlags; private static final String TAG = IwlanDataService.class.getSimpleName(); private static final String CONTEXT_ATTRIBUTION_TAG = "IWLAN"; @@ -126,6 +131,17 @@ public class IwlanDataService extends DataService { private static Transport sDefaultDataTransport = Transport.UNSPECIFIED_NETWORK; + private boolean mIs5GEnabledOnUi; + + public IwlanDataService() { + mFeatureFlags = new FeatureFlagsImpl(); + } + + @VisibleForTesting + IwlanDataService(FeatureFlags featureFlags) { + mFeatureFlags = featureFlags; + } + // TODO: see if network monitor callback impl can be shared between dataservice and // networkservice // This callback runs in the same thread as IwlanDataServiceHandler @@ -256,6 +272,15 @@ public class IwlanDataService extends DataService { private Date mUpStateTime = null; private boolean mIsImsOrEmergency; private DeactivateDataCallData mPendingDeactivateDataCallData; + private boolean mIsDataCallWithN1; + + public boolean getIsDataCallWithN1() { + return mIsDataCallWithN1; + } + + public void setIsDataCallWithN1(boolean mIsDataCallWithN1) { + this.mIsDataCallWithN1 = mIsDataCallWithN1; + } public int getPduSessionId() { return mPduSessionId; @@ -613,6 +638,7 @@ public class IwlanDataService extends DataService { events.add(IwlanEventListener.CROSS_SIM_CALLING_ENABLE_EVENT); events.add(IwlanEventListener.CELLINFO_CHANGED_EVENT); events.add(IwlanEventListener.CALL_STATE_CHANGED_EVENT); + events.add(IwlanEventListener.PREFERRED_NETWORK_TYPE_CHANGED_EVENT); IwlanEventListener.getInstance(mContext, slotIndex) .addEventListener(events, getIwlanDataServiceHandler()); } @@ -902,14 +928,21 @@ public class IwlanDataService extends DataService { } } - void forceCloseTunnels() { + /** + * Closes all tunnels forcefully for a specified reason. + * + * @param reason The reason for closing the tunnel. Must be {@link + * EpdgTunnelManager.TunnelBringDownReason}. + */ + void forceCloseTunnels(@EpdgTunnelManager.TunnelBringDownReason int reason) { for (Map.Entry<String, TunnelState> entry : mTunnelStateForApn.entrySet()) { getTunnelManager() .closeTunnel( entry.getKey(), true /* forceClose */, getIwlanTunnelCallback(), - getIwlanTunnelMetrics()); + getIwlanTunnelMetrics(), + reason); } } @@ -930,14 +963,15 @@ public class IwlanDataService extends DataService { } @VisibleForTesting - void setTunnelState( + protected void setTunnelState( DataProfile dataProfile, DataServiceCallback callback, int tunnelStatus, TunnelLinkProperties linkProperties, boolean isHandover, int pduSessionId, - boolean isImsOrEmergency) { + boolean isImsOrEmergency, + boolean isDataCallSetupWithN1) { TunnelState tunnelState = new TunnelState(callback); tunnelState.setState(tunnelStatus); tunnelState.setProtocolType(dataProfile.getApnSetting().getProtocol()); @@ -945,6 +979,7 @@ public class IwlanDataService extends DataService { tunnelState.setIsHandover(isHandover); tunnelState.setPduSessionId(pduSessionId); tunnelState.setIsImsOrEmergency(isImsOrEmergency); + tunnelState.setIsDataCallWithN1(isDataCallSetupWithN1); mTunnelStateForApn.put(dataProfile.getApnSetting().getApnName(), tunnelState); } @@ -1169,6 +1204,60 @@ public class IwlanDataService extends DataService { public void setCalendar(Calendar c) { mCalendar = c; } + + private boolean isPdnReestablishNeededOnIdleN1Update() { + return isN1ModeSupported() && (needIncludeN1ModeCapability() != mIs5GEnabledOnUi); + } + + private void disconnectPdnForN1ModeUpdate() { + if (hasActiveOrInitiatingDataCall()) { + forceCloseTunnels( + mIs5GEnabledOnUi + ? EpdgTunnelManager.BRINGDOWN_REASON_ENABLE_N1_MODE + : EpdgTunnelManager.BRINGDOWN_REASON_DISABLE_N1_MODE); + } + } + + private boolean hasActiveOrInitiatingDataCall() { + return mTunnelStateForApn.values().stream() + .anyMatch( + tunnelState -> + tunnelState.getState() == TunnelState.TUNNEL_UP + || tunnelState.getState() + == TunnelState.TUNNEL_IN_BRINGUP); + } + + // TODO(b/309867756): Include N1_MODE_CAPABILITY inclusion status in metrics. + private boolean needIncludeN1ModeCapability() { + if (!mFeatureFlags.updateN1ModeOnUiChange()) { + return isN1ModeSupported(); + } + if (!isN1ModeSupported()) { + return false; + } + // Maintain uniform N1_MODE_CAPABILITY Notify inclusion for all PDNs. + // Initiate PDN with current N1 inclusion in tunnel_up or tunnel_in_bringup states; + // otherwise, use UI settings. + return hasActiveOrInitiatingDataCall() ? isDataCallSetupWithN1() : mIs5GEnabledOnUi; + } + + private boolean isDataCallSetupWithN1() { + return mTunnelStateForApn.values().stream().anyMatch(TunnelState::getIsDataCallWithN1); + } + + protected boolean isN1ModeSupported() { + int[] nrAvailabilities = + IwlanHelper.getConfig( + CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, + mContext, + getSlotIndex()); + Log.d( + TAG, + "KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY : " + + Arrays.toString(nrAvailabilities)); + return Arrays.stream(nrAvailabilities) + .anyMatch(k -> k == CarrierConfigManager.CARRIER_NR_AVAILABILITY_SA); + } } private final class IwlanDataServiceHandler extends Handler { @@ -1437,7 +1526,29 @@ public class IwlanDataService extends DataService { iwlanDataServiceProvider = (IwlanDataServiceProvider) getDataServiceProvider(msg.arg1); - iwlanDataServiceProvider.mCallState = msg.arg2; + int previousCallState = iwlanDataServiceProvider.mCallState; + int currentCallState = iwlanDataServiceProvider.mCallState = msg.arg2; + + if (!mFeatureFlags.updateN1ModeOnUiChange()) { + break; + } + + // Disconnect PDN if call ends and re-establishment needed. + if (previousCallState != currentCallState + && currentCallState == TelephonyManager.CALL_STATE_IDLE + && iwlanDataServiceProvider.isPdnReestablishNeededOnIdleN1Update()) { + iwlanDataServiceProvider.disconnectPdnForN1ModeUpdate(); + } + break; + + case IwlanEventListener.PREFERRED_NETWORK_TYPE_CHANGED_EVENT: + if (!mFeatureFlags.updateN1ModeOnUiChange()) { + break; + } + iwlanDataServiceProvider = + (IwlanDataServiceProvider) getDataServiceProvider(msg.arg1); + long allowedNetworkType = (long) msg.obj; + onPreferredNetworkTypeChanged(iwlanDataServiceProvider, allowedNetworkType); break; case EVENT_SETUP_DATA_CALL: @@ -1454,6 +1565,7 @@ public class IwlanDataService extends DataService { if ((accessNetworkType != AccessNetworkType.IWLAN) || (dataProfile == null) + || (dataProfile.getApnSetting() == null) || (linkProperties == null && reason == DataService.REQUEST_REASON_HANDOVER)) { @@ -1501,8 +1613,7 @@ public class IwlanDataService extends DataService { dataProfile.getApnSetting().getApnName()); // Return the existing PDN if the pduSessionId is the same and the tunnel - // state is - // TUNNEL_UP. + // state is TUNNEL_UP. if (tunnelState != null) { if (tunnelState.getPduSessionId() == pduSessionId && tunnelState.getState() @@ -1542,11 +1653,16 @@ public class IwlanDataService extends DataService { return; } + boolean isDataCallSetupWithN1 = + iwlanDataServiceProvider.needIncludeN1ModeCapability(); + TunnelSetupRequest.Builder tunnelReqBuilder = TunnelSetupRequest.builder() .setApnName(dataProfile.getApnSetting().getApnName()) .setIsRoaming(isRoaming) - .setPduSessionId(pduSessionId) + .setPduSessionId( + isDataCallSetupWithN1 + ? pduSessionId : PDU_SESSION_ID_UNSET) .setApnIpProtocol( isRoaming ? dataProfile @@ -1581,7 +1697,8 @@ public class IwlanDataService extends DataService { null, (reason == DataService.REQUEST_REASON_HANDOVER), pduSessionId, - isIMS || isEmergency); + isIMS || isEmergency, + isDataCallSetupWithN1); boolean result = iwlanDataServiceProvider @@ -1623,7 +1740,7 @@ public class IwlanDataService extends DataService { case EVENT_FORCE_CLOSE_TUNNEL: for (IwlanDataServiceProvider dp : sIwlanDataServiceProviders.values()) { - dp.forceCloseTunnels(); + dp.forceCloseTunnels(EpdgTunnelManager.BRINGDOWN_REASON_UNKNOWN); } break; @@ -2165,11 +2282,37 @@ public class IwlanDataService extends DataService { return "EVENT_DEACTIVATE_DATA_CALL_WITH_DELAY"; case IwlanEventListener.CALL_STATE_CHANGED_EVENT: return "CALL_STATE_CHANGED_EVENT"; + case IwlanEventListener.PREFERRED_NETWORK_TYPE_CHANGED_EVENT: + return "PREFERRED_NETWORK_TYPE_CHANGED_EVENT"; default: return "Unknown(" + event + ")"; } } + private void initAllowedNetworkType() { + TelephonyManager mTelephonyManager = mContext.getSystemService(TelephonyManager.class); + mIs5GEnabledOnUi = + ((mTelephonyManager.getAllowedNetworkTypesBitmask() + & TelephonyManager.NETWORK_TYPE_BITMASK_NR) + != 0); + } + + private void onPreferredNetworkTypeChanged( + IwlanDataServiceProvider iwlanDataServiceProvider, long allowedNetworkType) { + boolean isCurrentUiEnable5G = + (allowedNetworkType & TelephonyManager.NETWORK_TYPE_BITMASK_NR) != 0; + boolean isPreviousUiEnable5G = mIs5GEnabledOnUi; + mIs5GEnabledOnUi = isCurrentUiEnable5G; + if (!iwlanDataServiceProvider.isN1ModeSupported()) { + return; + } + if (isPreviousUiEnable5G != isCurrentUiEnable5G) { + if (!iwlanDataServiceProvider.isOnCall()) { + iwlanDataServiceProvider.disconnectPdnForN1ModeUpdate(); + } + } + } + @Override public void onCreate() { Context context = getApplicationContext().createAttributionContext(CONTEXT_ATTRIBUTION_TAG); @@ -2177,6 +2320,7 @@ public class IwlanDataService extends DataService { IwlanBroadcastReceiver.startListening(mContext); IwlanCarrierConfigChangeListener.startListening(mContext); IwlanHelper.startCountryDetector(mContext); + initAllowedNetworkType(); } @Override diff --git a/src/com/google/android/iwlan/IwlanEventListener.java b/src/com/google/android/iwlan/IwlanEventListener.java index 114c66e..890afb4 100644 --- a/src/com/google/android/iwlan/IwlanEventListener.java +++ b/src/com/google/android/iwlan/IwlanEventListener.java @@ -39,6 +39,9 @@ import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; +import com.google.android.iwlan.flags.FeatureFlags; +import com.google.android.iwlan.flags.FeatureFlagsImpl; + import java.util.HashSet; import java.util.List; import java.util.Map; @@ -48,6 +51,7 @@ import java.util.concurrent.ConcurrentHashMap; public class IwlanEventListener { + private final FeatureFlags mFeatureFlags; public static final int UNKNOWN_EVENT = -1; /** On {@link IwlanCarrierConfigChangeListener#onCarrierConfigChanged} is called. */ @@ -88,6 +92,9 @@ public class IwlanEventListener { /** On Call state changed */ public static final int CALL_STATE_CHANGED_EVENT = 12; + /** On Preferred Network Type changed */ + public static final int PREFERRED_NETWORK_TYPE_CHANGED_EVENT = 13; + /* Events used and handled by IwlanDataService internally */ public static final int DATA_SERVICE_INTERNAL_EVENT_BASE = 100; @@ -106,7 +113,8 @@ public class IwlanEventListener { CROSS_SIM_CALLING_DISABLE_EVENT, CARRIER_CONFIG_UNKNOWN_CARRIER_EVENT, CELLINFO_CHANGED_EVENT, - CALL_STATE_CHANGED_EVENT + CALL_STATE_CHANGED_EVENT, + PREFERRED_NETWORK_TYPE_CHANGED_EVENT, }) @interface IwlanEventType {} @@ -148,7 +156,9 @@ public class IwlanEventListener { } private class RadioInfoTelephonyCallback extends TelephonyCallback - implements TelephonyCallback.CellInfoListener, TelephonyCallback.CallStateListener { + implements TelephonyCallback.CellInfoListener, + TelephonyCallback.CallStateListener, + TelephonyCallback.AllowedNetworkTypesListener { @Override public void onCellInfoChanged(List<CellInfo> arrayCi) { Log.d(LOG_TAG, "Cellinfo changed"); @@ -172,13 +182,32 @@ public class IwlanEventListener { instance.updateHandlers(CALL_STATE_CHANGED_EVENT, state); } } + + @Override + public void onAllowedNetworkTypesChanged( + @TelephonyManager.AllowedNetworkTypesReason int reason, + @TelephonyManager.NetworkTypeBitMask long allowedNetworkType) { + if (!mFeatureFlags.updateN1ModeOnUiChange()) { + return; + } + + if (reason != TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER) { + return; + } + + IwlanEventListener instance = mInstances.get(mSlotId); + if (instance != null) { + instance.updateHandlers(PREFERRED_NETWORK_TYPE_CHANGED_EVENT, allowedNetworkType); + } + } } /** * Returns IwlanEventListener instance */ public static IwlanEventListener getInstance(@NonNull Context context, int slotId) { - return mInstances.computeIfAbsent(slotId, k -> new IwlanEventListener(context, slotId)); + return mInstances.computeIfAbsent( + slotId, k -> new IwlanEventListener(context, slotId, new FeatureFlagsImpl())); } @VisibleForTesting @@ -371,16 +400,20 @@ public class IwlanEventListener { case "CELLINFO_CHANGED_EVENT": ret = CELLINFO_CHANGED_EVENT; break; + case "PREFERRED_NETWORK_TYPE_CHANGED_EVENT": + ret = PREFERRED_NETWORK_TYPE_CHANGED_EVENT; + break; } return ret; } - private IwlanEventListener(Context context, int slotId) { + IwlanEventListener(Context context, int slotId, FeatureFlags featureFlags) { mContext = context; mSlotId = slotId; mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; SUB_TAG = IwlanEventListener.class.getSimpleName() + "[" + slotId + "]"; sIsAirplaneModeOn = null; + mFeatureFlags = featureFlags; } private void onCarrierConfigChanged(int subId, int carrierId) { @@ -540,6 +573,16 @@ public class IwlanEventListener { } } + private synchronized void updateHandlers(int event, long allowedNetworkType) { + if (eventHandlers.contains(event)) { + Log.d(SUB_TAG, "Updating handlers for the event: " + event); + for (Handler handler : eventHandlers.get(event)) { + handler.obtainMessage(event, mSlotId, 0 /* unused */, allowedNetworkType) + .sendToTarget(); + } + } + } + private String callStateToString(int state) { switch (state) { case TelephonyManager.CALL_STATE_IDLE: diff --git a/src/com/google/android/iwlan/epdg/EpdgSelector.java b/src/com/google/android/iwlan/epdg/EpdgSelector.java index e55467b..c231d50 100644 --- a/src/com/google/android/iwlan/epdg/EpdgSelector.java +++ b/src/com/google/android/iwlan/epdg/EpdgSelector.java @@ -83,6 +83,10 @@ public class EpdgSelector { private List<byte[]> mV6PcoData; @NonNull private final ErrorPolicyManager mErrorPolicyManager; + // Temporary excluded IP addresses due to recent failures. Cleared after tunnel opened + // successfully or all resolved IP addresses are tried and excluded. + private final Set<InetAddress> mTemporaryExcludedAddresses; + // The default DNS timeout in the DNS module is set to 5 seconds. To account for IPC overhead, // IWLAN applies an internal timeout of 6 seconds, slightly longer than the default timeout private static final long DNS_RESOLVER_TIMEOUT_DURATION_SEC = 6L; @@ -153,6 +157,8 @@ public class EpdgSelector { mV6PcoData = new ArrayList<>(); mErrorPolicyManager = ErrorPolicyManager.getInstance(mContext, mSlotId); + + mTemporaryExcludedAddresses = new HashSet<>(); initializeExecutors(); } @@ -235,6 +241,45 @@ public class EpdgSelector { mV6PcoData.clear(); } + /** + * Notify {@link EpdgSelector} that ePDG is connected successfully. The excluded ip addresses + * will be cleared so that next ePDG selection will retry all ip addresses. + */ + void onEpdgConnectedSuccessfully() { + clearExcludedIpAddresses(); + } + + /** + * Notify {@link EpdgSelector} that failed to connect to an ePDG. EpdgSelector will add the + * {@code ipAddress} into excluded list and will not retry until any ePDG connected successfully + * or all ip addresses candidates are tried. + * + * @param ipAddress the ePDG ip address that failed to connect + */ + void onEpdgConnectionFailed(InetAddress ipAddress) { + excludeIpAddress(ipAddress); + } + + private void excludeIpAddress(InetAddress ipAddress) { + if (!mFeatureFlags.epdgSelectionExcludeFailedIpAddress()) { + return; + } + Log.d(TAG, "Added " + ipAddress + " into temporary excluded addresses"); + mTemporaryExcludedAddresses.add(ipAddress); + } + + private void clearExcludedIpAddresses() { + if (!mFeatureFlags.epdgSelectionExcludeFailedIpAddress()) { + return; + } + Log.d(TAG, "Cleared temporary excluded addresses"); + mTemporaryExcludedAddresses.clear(); + } + + private boolean isInExcludedIpAddresses(InetAddress ipAddress) { + return mTemporaryExcludedAddresses.contains(ipAddress); + } + private CompletableFuture<Map.Entry<String, List<InetAddress>>> submitDnsResolverQuery( String domainName, Network network, int queryType, Executor executor) { CompletableFuture<Map.Entry<String, List<InetAddress>>> result = new CompletableFuture(); @@ -326,6 +371,36 @@ public class EpdgSelector { Log.d(TAG, domain + ": " + domainNameToIpAddresses.get(domain)); } } + + private List<InetAddress> filterExcludedAddresses(List<InetAddress> ipList) { + if (!mFeatureFlags.epdgSelectionExcludeFailedIpAddress()) { + return ipList; + } + if (mTemporaryExcludedAddresses.containsAll(ipList)) { + Log.d( + TAG, + "All valid ip are tried and excluded, clear all" + + " excluded address and retry entire list again"); + clearExcludedIpAddresses(); + } + + var filteredIpList = + ipList.stream().filter(ipAddress -> !isInExcludedIpAddresses(ipAddress)).toList(); + + int excludedIpNum = filteredIpList.size() - ipList.size(); + if (excludedIpNum > 0) { + Log.d( + TAG, + "Excluded " + + excludedIpNum + + " out of " + + ipList.size() + + " addresses from the list due to recent failures"); + } + + return filteredIpList; + } + /** * Returns a list of unique IP addresses corresponding to the given domain names, in the same * order of the input. Runs DNS resolution across parallel threads. @@ -562,19 +637,19 @@ public class EpdgSelector { return resultIpList; } - private void prioritizeIp(@NonNull List<InetAddress> validIpList, @EpdgAddressOrder int order) { - switch (order) { - case IPV4_PREFERRED: - validIpList.sort(inetAddressComparator); - break; - case IPV6_PREFERRED: - validIpList.sort(inetAddressComparator.reversed()); - break; - case SYSTEM_PREFERRED: - break; - default: + private List<InetAddress> prioritizeIp( + @NonNull List<InetAddress> validIpList, @EpdgAddressOrder int order) { + return switch (order) { + case IPV4_PREFERRED -> validIpList.stream().sorted(inetAddressComparator).toList(); + case IPV6_PREFERRED -> validIpList.stream() + .sorted(inetAddressComparator.reversed()) + .toList(); + case SYSTEM_PREFERRED -> validIpList; + default -> { Log.w(TAG, "Invalid EpdgAddressOrder : " + order); - } + yield validIpList; + } + }; } private String[] splitMccMnc(String plmn) { @@ -1290,9 +1365,10 @@ public class EpdgSelector { } if (!validIpList.isEmpty()) { - prioritizeIp(validIpList, order); - selectorCallback.onServerListChanged( - transactionId, removeDuplicateIp(validIpList)); + validIpList = removeDuplicateIp(validIpList); + validIpList = filterExcludedAddresses(validIpList); + validIpList = prioritizeIp(validIpList, order); + selectorCallback.onServerListChanged(transactionId, validIpList); } else { selectorCallback.onError( transactionId, diff --git a/src/com/google/android/iwlan/epdg/EpdgTunnelManager.java b/src/com/google/android/iwlan/epdg/EpdgTunnelManager.java index aa2b0da..fc9be74 100644 --- a/src/com/google/android/iwlan/epdg/EpdgTunnelManager.java +++ b/src/com/google/android/iwlan/epdg/EpdgTunnelManager.java @@ -18,6 +18,7 @@ package com.google.android.iwlan.epdg; import static android.net.ipsec.ike.ike3gpp.Ike3gppData.DATA_TYPE_NOTIFY_BACKOFF_TIMER; import static android.net.ipsec.ike.ike3gpp.Ike3gppData.DATA_TYPE_NOTIFY_N1_MODE_INFORMATION; +import static android.net.ipsec.ike.ike3gpp.Ike3gppParams.PDU_SESSION_ID_UNSET; import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.AF_INET6; @@ -62,6 +63,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; +import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.telephony.CarrierConfigManager; @@ -236,12 +238,34 @@ public class EpdgTunnelManager { SaProposal.PSEUDORANDOM_FUNCTION_SHA2_512); } - private static final int PDU_SESSION_ID_UNSET = 0; - // TODO(b/239753287): Some networks request DEVICE_IDENTITY, but errors out when parsing // the response. Temporarily disabled. private static final boolean INCLUDE_DEVICE_IDENTITY = false; + public static final int BRINGDOWN_REASON_UNKNOWN = 0; + public static final int BRINGDOWN_REASON_DISABLE_N1_MODE = 1; + public static final int BRINGDOWN_REASON_ENABLE_N1_MODE = 2; + + @IntDef({ + BRINGDOWN_REASON_UNKNOWN, + BRINGDOWN_REASON_DISABLE_N1_MODE, + BRINGDOWN_REASON_ENABLE_N1_MODE, + }) + public @interface TunnelBringDownReason {} + + private static String bringdownReasonToString(@TunnelBringDownReason int reason) { + switch (reason) { + case BRINGDOWN_REASON_UNKNOWN: + return "BRINGDOWN_REASON_UNKNOWN"; + case BRINGDOWN_REASON_DISABLE_N1_MODE: + return "BRINGDOWN_REASON_DISABLE_N1_MODE"; + case BRINGDOWN_REASON_ENABLE_N1_MODE: + return "BRINGDOWN_REASON_ENABLE_N1_MODE"; + default: + return "Unknown(" + reason + ")"; + } + } + private final EpdgSelector.EpdgSelectorCallback mSelectorCallback = new EpdgSelector.EpdgSelectorCallback() { @Override @@ -682,16 +706,37 @@ public class EpdgTunnelManager { * @param tunnelCallback Used if no current or pending IWLAN tunnel exists * @param iwlanTunnelMetrics Used to report metrics if no current or pending IWLAN tunnel exists */ + // TODO(b/309866889): Clarify tunnel bring down reason for tunnel closure. public void closeTunnel( @NonNull String apnName, boolean forceClose, @NonNull TunnelCallback tunnelCallback, @NonNull IwlanTunnelMetricsImpl iwlanTunnelMetrics) { + closeTunnel( + apnName, forceClose, tunnelCallback, iwlanTunnelMetrics, BRINGDOWN_REASON_UNKNOWN); + } + + /** + * Closes tunnel for an apn with reason. + * + * @param apnName APN name + * @param forceClose if {@code true}, triggers a local cleanup of the tunnel; if {@code false}, + * performs a normal closure procedure + * @param tunnelCallback The tunnelCallback for tunnel to be closed + * @param iwlanTunnelMetrics The metrics to be reported + * @param reason The reason for tunnel to be closed + */ + public void closeTunnel( + @NonNull String apnName, + boolean forceClose, + @NonNull TunnelCallback tunnelCallback, + @NonNull IwlanTunnelMetricsImpl iwlanTunnelMetrics, + @TunnelBringDownReason int reason) { mHandler.sendMessage( mHandler.obtainMessage( EVENT_TUNNEL_BRINGDOWN_REQUEST, new TunnelBringdownRequest( - apnName, forceClose, tunnelCallback, iwlanTunnelMetrics))); + apnName, forceClose, tunnelCallback, iwlanTunnelMetrics, reason))); } /** @@ -1128,9 +1173,9 @@ public class EpdgTunnelManager { } } - if (isN1ModeSupported() && setupRequest.pduSessionId() != PDU_SESSION_ID_UNSET) { - // Configures the PduSession ID in N1_MODE_CAPABILITY payload - // to notify the server that UE supports N1_MODE + if (setupRequest.pduSessionId() != PDU_SESSION_ID_UNSET) { + // Includes N1_MODE_CAPABILITY NOTIFY payload in IKE_AUTH exchange when PDU session ID + // is set; otherwise, do not include. builder3gppParams.setPduSessionId((byte) setupRequest.pduSessionId()); } @@ -1753,6 +1798,7 @@ public class EpdgTunnelManager { tunnelConfig.getTunnelCallback().onOpened(apnName, linkProperties); reportIwlanError(apnName, new IwlanError(IwlanError.NO_ERROR)); + getEpdgSelector().onEpdgConnectedSuccessfully(); mIkeTunnelEstablishmentDuration = System.currentTimeMillis() - mIkeTunnelEstablishmentStartTime; @@ -1822,6 +1868,8 @@ public class EpdgTunnelManager { } else { reportIwlanError(apnName, iwlanError); } + + getEpdgSelector().onEpdgConnectionFailed(mEpdgAddress); } Log.d(TAG, "Tunnel Closed: " + iwlanError); @@ -1905,14 +1953,17 @@ public class EpdgTunnelManager { TunnelBringdownRequest bringdownRequest = (TunnelBringdownRequest) msg.obj; apnName = bringdownRequest.mApnName; boolean forceClose = bringdownRequest.mForceClose; + int reason = bringdownRequest.mBringDownReason; tunnelConfig = mApnNameToTunnelConfig.get(apnName); if (tunnelConfig == null) { Log.w( TAG, "Bringdown request: No tunnel exists for apn: " + apnName - + "forced: " - + forceClose); + + ", forced: " + + forceClose + + ", bringdown reason: " + + bringdownReasonToString(reason)); } else { if (forceClose) { tunnelConfig.getIkeSession().kill(); @@ -1920,9 +1971,17 @@ public class EpdgTunnelManager { tunnelConfig.getIkeSession().close(); } } + // TODO(b/309867892): Include tunnel bring down reason in metrics. int numClosed = closePendingRequestsForApn(apnName); if (numClosed > 0) { - Log.d(TAG, "Closed " + numClosed + " pending requests for apn: " + apnName); + Log.d( + TAG, + "Closed " + + numClosed + + " pending requests for apn: " + + apnName + + ", bringdown reason: " + + bringdownReasonToString(reason)); } if (tunnelConfig == null && numClosed == 0) { // IwlanDataService expected to close a (pending or up) tunnel but was not @@ -2181,6 +2240,17 @@ public class EpdgTunnelManager { @VisibleForTesting void validateAndSetEpdgAddress(List<InetAddress> selectorResultList) { + if (mFeatureFlags.epdgSelectionExcludeFailedIpAddress()) { + Log.d( + TAG, + "Selected first ePDG address " + + selectorResultList.get(0) + + " from available ePDG address list: " + + Arrays.toString(selectorResultList.toArray())); + mValidEpdgInfo.setAddrList(selectorResultList); + mEpdgAddress = selectorResultList.get(0); + return; + } List<InetAddress> addrList = mValidEpdgInfo.getAddrList(); if (addrList == null || !addrList.equals(selectorResultList)) { Log.d(TAG, "Update ePDG address list."); @@ -2287,16 +2357,19 @@ public class EpdgTunnelManager { final boolean mForceClose; final TunnelCallback mTunnelCallback; final IwlanTunnelMetricsImpl mIwlanTunnelMetrics; + final int mBringDownReason; private TunnelBringdownRequest( String apnName, boolean forceClose, TunnelCallback tunnelCallback, - IwlanTunnelMetricsImpl iwlanTunnelMetrics) { + IwlanTunnelMetricsImpl iwlanTunnelMetrics, + @TunnelBringDownReason int reason) { mApnName = apnName; mForceClose = forceClose; mTunnelCallback = tunnelCallback; mIwlanTunnelMetrics = iwlanTunnelMetrics; + mBringDownReason = reason; } } @@ -2569,15 +2642,6 @@ public class EpdgTunnelManager { } @VisibleForTesting - boolean isN1ModeSupported() { - int[] nrCarrierCaps = - getConfig(CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY); - Log.d(TAG, "KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY : " + Arrays.toString(nrCarrierCaps)); - return Arrays.stream(nrCarrierCaps) - .anyMatch(cap -> cap == CarrierConfigManager.CARRIER_NR_AVAILABILITY_SA); - } - - @VisibleForTesting boolean isTunnelConfigContainExistApn(String apnName) { return mApnNameToTunnelConfig.containsKey(apnName); } diff --git a/src/com/google/android/iwlan/epdg/TunnelSetupRequest.java b/src/com/google/android/iwlan/epdg/TunnelSetupRequest.java index d1ed9dd..299ed71 100644 --- a/src/com/google/android/iwlan/epdg/TunnelSetupRequest.java +++ b/src/com/google/android/iwlan/epdg/TunnelSetupRequest.java @@ -16,7 +16,7 @@ package com.google.android.iwlan.epdg; -import android.net.Network; +import com.android.internal.annotations.VisibleForTesting; import com.google.auto.value.AutoValue; @@ -43,6 +43,11 @@ public abstract class TunnelSetupRequest { abstract boolean requestPcscf(); + @VisibleForTesting + public int getPduSessionId() { + return pduSessionId(); + } + public static Builder builder() { return new AutoValue_TunnelSetupRequest.Builder() .setSrcIpv4Address(Optional.empty()) diff --git a/test/com/google/android/iwlan/ErrorPolicyManagerTest.java b/test/com/google/android/iwlan/ErrorPolicyManagerTest.java index b829068..eae41bb 100644 --- a/test/com/google/android/iwlan/ErrorPolicyManagerTest.java +++ b/test/com/google/android/iwlan/ErrorPolicyManagerTest.java @@ -895,6 +895,65 @@ public class ErrorPolicyManagerTest { } @Test + public void testWifiApChangedUnthrottle() throws Exception { + String apn = "ims"; + String config = + "[{" + + "\"ApnName\": \"" + + apn + + "\"," + + "\"ErrorTypes\": [{" + + ErrorPolicyString.builder() + .setErrorType("IKE_PROTOCOL_ERROR_TYPE") + .setErrorDetails(List.of("24", "34")) + .setRetryArray(List.of("6", "12", "24")) + .setUnthrottlingEvents( + List.of( + "WIFI_CALLING_DISABLE_EVENT", + "WIFI_DISABLE_EVENT", + "WIFI_AP_CHANGED_EVENT")) + .build() + .getErrorPolicyInString() + + "}, {" + + ErrorPolicyString.builder() + .setErrorType("GENERIC_ERROR_TYPE") + .setErrorDetails(List.of("SERVER_SELECTION_FAILED")) + .setRetryArray(List.of("0")) + .setUnthrottlingEvents(List.of("APM_ENABLE_EVENT")) + .build() + .getErrorPolicyInString() + + "}]" + + "}]"; + PersistableBundle bundle = new PersistableBundle(); + bundle.putString(ErrorPolicyManager.KEY_ERROR_POLICY_CONFIG_STRING, config); + setupMockForCarrierConfig(bundle); + mErrorPolicyManager + .mHandler + .obtainMessage(IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT) + .sendToTarget(); + mTestLooper.dispatchAll(); + + // IKE_PROTOCOL_ERROR_TYPE(24) and retryArray = 6, 12, 24 + IwlanError iwlanError = buildIwlanIkeAuthFailedError(); + long time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); + assertEquals(6, time); + + mErrorPolicyManager + .mHandler + .obtainMessage(IwlanEventListener.WIFI_AP_CHANGED_EVENT) + .sendToTarget(); + advanceClockByTimeMs(500); + verify(mMockDataServiceProvider, times(1)).notifyApnUnthrottled(eq(apn)); + + boolean bringUpTunnel = mErrorPolicyManager.canBringUpTunnel(apn); + assertTrue(bringUpTunnel); + + iwlanError = buildIwlanIkeAuthFailedError(); + time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); + assertEquals(6, time); + } + + @Test public void testGetDataFailCauseRetryTime() throws Exception { String apn1 = "ims"; String apn2 = "mms"; diff --git a/test/com/google/android/iwlan/IwlanDataServiceTest.java b/test/com/google/android/iwlan/IwlanDataServiceTest.java index c2b4e28..dc2b2cf 100644 --- a/test/com/google/android/iwlan/IwlanDataServiceTest.java +++ b/test/com/google/android/iwlan/IwlanDataServiceTest.java @@ -19,6 +19,11 @@ package com.google.android.iwlan; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.net.ipsec.ike.ike3gpp.Ike3gppParams.PDU_SESSION_ID_UNSET; +import static android.telephony.TelephonyManager.CALL_STATE_IDLE; +import static android.telephony.TelephonyManager.CALL_STATE_RINGING; +import static android.telephony.TelephonyManager.NETWORK_TYPE_BITMASK_LTE; +import static android.telephony.TelephonyManager.NETWORK_TYPE_BITMASK_NR; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; @@ -56,8 +61,10 @@ import android.net.NetworkCapabilities; import android.net.TelephonyNetworkSpecifier; import android.net.ipsec.ike.exceptions.IkeInternalException; import android.net.vcn.VcnTransportInfo; +import android.os.PersistableBundle; import android.os.test.TestLooper; import android.telephony.AccessNetworkConstants.AccessNetworkType; +import android.telephony.CarrierConfigManager; import android.telephony.DataFailCause; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; @@ -69,6 +76,7 @@ import android.telephony.data.DataService; import android.telephony.data.DataServiceCallback; import android.telephony.data.IDataServiceCallback; import android.telephony.data.NetworkSliceInfo; +import android.telephony.data.TrafficDescriptor; import android.telephony.ims.ImsManager; import android.telephony.ims.ImsMmTelManager; @@ -80,6 +88,7 @@ import com.google.android.iwlan.epdg.EpdgTunnelManager; import com.google.android.iwlan.epdg.NetworkSliceSelectionAssistanceInformation; import com.google.android.iwlan.epdg.TunnelLinkProperties; import com.google.android.iwlan.epdg.TunnelSetupRequest; +import com.google.android.iwlan.flags.FeatureFlags; import com.google.android.iwlan.proto.MetricsAtom; import org.junit.After; @@ -133,6 +142,8 @@ public class IwlanDataServiceTest { @Mock private LinkAddress mMockIPv6LinkAddress; @Mock private Inet4Address mMockInet4Address; @Mock private Inet6Address mMockInet6Address; + @Mock private CarrierConfigManager mMockCarrierConfigManager; + @Mock private FeatureFlags mFakeFeatureFlags; MockitoSession mStaticMockSession; @@ -249,7 +260,10 @@ public class IwlanDataServiceTest { when(mMockIPv4LinkAddress.getAddress()).thenReturn(mMockInet4Address); when(mMockIPv6LinkAddress.getAddress()).thenReturn(mMockInet6Address); - mIwlanDataService = spy(new IwlanDataService()); + mIwlanDataService = spy(new IwlanDataService(mFakeFeatureFlags)); + + when(mMockContext.getSystemService(eq(CarrierConfigManager.class))) + .thenReturn(mMockCarrierConfigManager); // Injects the test looper into the IwlanDataServiceHandler doReturn(mTestLooper.getLooper()).when(mIwlanDataService).getLooper(); mIwlanDataService.setAppContext(mMockContext); @@ -434,7 +448,8 @@ public class IwlanDataServiceTest { null, /* linkProperties */ false /* isHandover */, 1 /* pduSessionId */, - true /* isImsOrEmergency */); + true /* isImsOrEmergency */, + true /* isDataCallSetupWithN1 */); LinkProperties newLinkProperties = new LinkProperties(mLinkProperties); newLinkProperties.setInterfaceName("wlan0"); @@ -600,7 +615,8 @@ public class IwlanDataServiceTest { mLinkProperties, false, /* isHandover */ 1, /* pduSessionId */ - true /* isImsOrEmergency */); + true /* isImsOrEmergency */, + true /* isDataCallSetupWithN1 */); mSpyIwlanDataServiceProvider.requestDataCallList(new DataServiceCallback(callback)); mTestLooper.dispatchAll(); @@ -751,6 +767,33 @@ public class IwlanDataServiceTest { } @Test + public void testIwlanSetupDataCallWithBringUpTunnelAndNullApnSetting() { + DataProfile dp = buildImsDataProfileWithEmptyApnSetting(); + + /* Wifi is connected */ + onSystemDefaultNetworkConnected( + mMockNetwork, mLinkProperties, TRANSPORT_WIFI, INVALID_SUB_INDEX); + + mSpyIwlanDataServiceProvider.setupDataCall( + AccessNetworkType.IWLAN, /* AccessNetworkType */ + dp, /* dataProfile */ + false, /* isRoaming */ + true, /* allowRoaming */ + DataService.REQUEST_REASON_NORMAL, /* DataService.REQUEST_REASON_NORMAL */ + null, /* LinkProperties */ + 1, /* pduSessionId */ + null, /* sliceInfo */ + null, /* trafficDescriptor */ + true, /* matchAllRuleAllowed */ + mMockDataServiceCallback); + mTestLooper.dispatchAll(); + + verify(mMockDataServiceCallback, times(1)) + .onSetupDataCallComplete( + eq(DataServiceCallback.RESULT_ERROR_INVALID_ARG), isNull()); + } + + @Test public void testSliceInfoInclusionInDataCallResponse() throws Exception { DataProfile dp = buildImsDataProfile(); @@ -810,7 +853,8 @@ public class IwlanDataServiceTest { null, /* linkProperties */ false, /* isHandover */ 1, /* pduSessionId */ - true /* isImsOrEmergency */); + true /* isImsOrEmergency */, + true /* isDataCallSetupWithN1 */); mSpyIwlanDataServiceProvider.deactivateDataCall( TEST_APN_NAME.hashCode() /* cid: hashcode() of "ims" */, @@ -847,7 +891,8 @@ public class IwlanDataServiceTest { null, /* linkProperties */ false, /* isHandover */ 1, /* pduSessionId */ - true /* isImsOrEmergency */); + true /* isImsOrEmergency */, + true /* isDataCallSetupWithN1 */); mSpyIwlanDataServiceProvider.deactivateDataCall( TEST_APN_NAME.hashCode() /* cid: hashcode() of "ims" */, @@ -891,7 +936,8 @@ public class IwlanDataServiceTest { null, /* linkProperties */ false, /* isHandover */ 1, /* pduSessionId */ - true /* isImsOrEmergency */); + true /* isImsOrEmergency */, + true /* isDataCallSetupWithN1 */); mSpyIwlanDataServiceProvider.deactivateDataCall( TEST_APN_NAME.hashCode() /* cid: hashcode() of "ims" */, @@ -949,7 +995,8 @@ public class IwlanDataServiceTest { null, /* linkProperties */ false, /* isHandover */ 1, /* pduSessionId */ - true /* isImsOrEmergency */); + true /* isImsOrEmergency */, + true /* isDataCallSetupWithN1 */); mSpyIwlanDataServiceProvider.setMetricsAtom( TEST_APN_NAME, @@ -1014,7 +1061,8 @@ public class IwlanDataServiceTest { null, /* linkProperties */ (setupDataReason == DataService.REQUEST_REASON_HANDOVER), 1 /* pduSessionId */, - true /* isImsOrEmergency */); + true /* isImsOrEmergency */, + true /* isDataCallSetupWithN1 */); mSpyIwlanDataServiceProvider.setMetricsAtom( TEST_APN_NAME, @@ -1066,7 +1114,8 @@ public class IwlanDataServiceTest { null, /* linkProperties */ (setupDataReason == DataService.REQUEST_REASON_HANDOVER), 1 /* pduSessionId */, - true /* isImsOrEmergency */); + true /* isImsOrEmergency */, + true /* isDataCallSetupWithN1 */); mSpyIwlanDataServiceProvider.setMetricsAtom( TEST_APN_NAME, @@ -1117,7 +1166,7 @@ public class IwlanDataServiceTest { .obtainMessage( IwlanEventListener.CALL_STATE_CHANGED_EVENT, DEFAULT_SLOT_INDEX, - TelephonyManager.CALL_STATE_IDLE) + CALL_STATE_IDLE) .sendToTarget(); mSpyIwlanDataServiceProvider.setTunnelState( @@ -1127,7 +1176,8 @@ public class IwlanDataServiceTest { null, /* linkProperties */ (setupDataReason == DataService.REQUEST_REASON_HANDOVER), 1 /* pduSessionId */, - true /* isImsOrEmergency */); + true /* isImsOrEmergency */, + true /* isDataCallSetupWithN1 */); mSpyIwlanDataServiceProvider.setMetricsAtom( TEST_APN_NAME, @@ -1175,7 +1225,7 @@ public class IwlanDataServiceTest { .obtainMessage( IwlanEventListener.CALL_STATE_CHANGED_EVENT, DEFAULT_SLOT_INDEX, - TelephonyManager.CALL_STATE_IDLE) + CALL_STATE_IDLE) .sendToTarget(); mSpyIwlanDataServiceProvider.setTunnelState( @@ -1185,7 +1235,8 @@ public class IwlanDataServiceTest { null, /* linkProperties */ (setupDataReason == DataService.REQUEST_REASON_HANDOVER), 1 /* pduSessionId */, - true /* isImsOrEmergency */); + true /* isImsOrEmergency */, + true /* isDataCallSetupWithN1 */); mSpyIwlanDataServiceProvider.setMetricsAtom( TEST_APN_NAME, @@ -1243,7 +1294,8 @@ public class IwlanDataServiceTest { null /* linkProperties */, (setupDataReason == DataService.REQUEST_REASON_HANDOVER), 1 /* pduSessionId */, - true /* isImsOrEmergency */); + true /* isImsOrEmergency */, + true /* isDataCallSetupWithN1 */); mSpyIwlanDataServiceProvider.setMetricsAtom( TEST_APN_NAME, @@ -1301,7 +1353,8 @@ public class IwlanDataServiceTest { null /* linkProperties */, (setupDataReason == DataService.REQUEST_REASON_HANDOVER), 1 /* pduSessionId */, - true /* isImsOrEmergency */); + true /* isImsOrEmergency */, + true /* isDataCallSetupWithN1 */); mSpyIwlanDataServiceProvider.setMetricsAtom( TEST_APN_NAME, @@ -1394,6 +1447,16 @@ public class IwlanDataServiceTest { mTestLooper.dispatchAll(); } + private DataProfile buildImsDataProfileWithEmptyApnSetting() { + return new DataProfile.Builder() + .setTrafficDescriptor( + new TrafficDescriptor.Builder().setDataNetworkName("").build()) + .setType(1) + .enable(true) + .setPreferred(true) + .build(); + } + private DataProfile buildImsDataProfile() { return buildDataProfile(ApnSetting.TYPE_IMS); } @@ -1653,7 +1716,8 @@ public class IwlanDataServiceTest { null /* linkProperties */, false /* isHandover */, 1 /* pduSessionId */, - true /* isImsOrEmergency */); + true /* isImsOrEmergency */, + true /* isDataCallSetupWithN1 */); mSpyIwlanDataServiceProvider.setMetricsAtom( TEST_APN_NAME, @@ -1671,7 +1735,7 @@ public class IwlanDataServiceTest { .thenReturn(DataFailCause.ERROR_UNSPECIFIED); // Simulate IwlanDataService.onUnbind() which force close all tunnels - mSpyIwlanDataServiceProvider.forceCloseTunnels(); + mSpyIwlanDataServiceProvider.forceCloseTunnels(EpdgTunnelManager.BRINGDOWN_REASON_UNKNOWN); // Simulate DataService.onUnbind() which remove all IwlanDataServiceProviders mSpyIwlanDataServiceProvider.close(); mTestLooper.dispatchAll(); @@ -1681,7 +1745,8 @@ public class IwlanDataServiceTest { eq(TEST_APN_NAME), eq(true), any(IwlanTunnelCallback.class), - any(IwlanTunnelMetricsImpl.class)); + any(IwlanTunnelMetricsImpl.class), + eq(EpdgTunnelManager.BRINGDOWN_REASON_UNKNOWN)); assertNotNull(mIwlanDataService.mIwlanDataServiceHandler); // Should not raise NullPointerException mSpyIwlanDataServiceProvider @@ -1707,7 +1772,8 @@ public class IwlanDataServiceTest { null, /* linkProperties */ false /* isHandover */, 1 /* pduSessionId */, - true /* isImsOrEmergency */); + true /* isImsOrEmergency */, + true /* isDataCallSetupWithN1 */); mSpyIwlanDataServiceProvider.setMetricsAtom( TEST_APN_NAME, @@ -1774,7 +1840,8 @@ public class IwlanDataServiceTest { null, /* linkProperties */ false /* isHandover */, 1 /* pduSessionId */, - true /* isImsOrEmergency */); + true /* isImsOrEmergency */, + true /* isDataCallSetupWithN1 */); mSpyIwlanDataServiceProvider.setMetricsAtom( TEST_APN_NAME, @@ -1817,7 +1884,8 @@ public class IwlanDataServiceTest { null, /* linkProperties */ false /* isHandover */, 1 /* pduSessionId */, - true /* isImsOrEmergency */); + true /* isImsOrEmergency */, + true /* isDataCallSetupWithN1 */); mSpyIwlanDataServiceProvider.setMetricsAtom( TEST_APN_NAME, @@ -2003,4 +2071,249 @@ public class IwlanDataServiceTest { .setSliceInfo(SLICE_INFO) .build(); } + + private void setupMockForGetConfig(PersistableBundle bundle) { + if (bundle == null) { + bundle = new PersistableBundle(); + } + when(mMockContext.getSystemService(eq(CarrierConfigManager.class))) + .thenReturn(mMockCarrierConfigManager); + when(mMockCarrierConfigManager.getConfigForSubId(DEFAULT_SLOT_INDEX)).thenReturn(bundle); + } + + private void mockCarrierConfigForN1Mode(boolean supportN1Mode) { + PersistableBundle bundle = new PersistableBundle(); + if (supportN1Mode) { + bundle.putIntArray( + CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, + new int[] { + CarrierConfigManager.CARRIER_NR_AVAILABILITY_NSA, + CarrierConfigManager.CARRIER_NR_AVAILABILITY_SA + }); + } else { + bundle.putIntArray( + CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, + new int[] {CarrierConfigManager.CARRIER_NR_AVAILABILITY_NSA}); + } + setupMockForGetConfig(bundle); + } + + private void mockCallState(int callState) { + onSystemDefaultNetworkConnected(TRANSPORT_CELLULAR); + + mIwlanDataService + .mIwlanDataServiceHandler + .obtainMessage( + IwlanEventListener.CALL_STATE_CHANGED_EVENT, DEFAULT_SLOT_INDEX, callState) + .sendToTarget(); + + mSpyIwlanDataServiceProvider.setMetricsAtom( + TEST_APN_NAME, 64, true, TelephonyManager.NETWORK_TYPE_LTE, false, true, 1); + } + + private void updatePreferredNetworkType(long networkTypeBitmask) { + mIwlanDataService + .mIwlanDataServiceHandler + .obtainMessage( + IwlanEventListener.PREFERRED_NETWORK_TYPE_CHANGED_EVENT, + DEFAULT_SLOT_INDEX, + 0 /* unused */, + networkTypeBitmask) + .sendToTarget(); + mTestLooper.dispatchAll(); + } + + @Test + public void testIsN1ModeSupported() { + PersistableBundle bundle = new PersistableBundle(); + bundle.putIntArray( + CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, + new int[] { + CarrierConfigManager.CARRIER_NR_AVAILABILITY_NSA, + CarrierConfigManager.CARRIER_NR_AVAILABILITY_SA + }); + setupMockForGetConfig(bundle); + assertTrue(mSpyIwlanDataServiceProvider.isN1ModeSupported()); + + bundle.putIntArray( + CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, + new int[] { + CarrierConfigManager.CARRIER_NR_AVAILABILITY_NSA, + }); + setupMockForGetConfig(bundle); + assertFalse(mSpyIwlanDataServiceProvider.isN1ModeSupported()); + } + + @Test + public void testMultipleAllowedNetworkTypeChangeInIdle_updateN1Mode() throws Exception { + when(mFakeFeatureFlags.updateN1ModeOnUiChange()).thenReturn(true); + mockCarrierConfigForN1Mode(true); + mockCallState(CALL_STATE_IDLE); + mockSetupDataCallWithPduSessionId(0); + updatePreferredNetworkType(NETWORK_TYPE_BITMASK_NR); + + /* Check closeTunnel() is called. */ + verify(mMockEpdgTunnelManager, atLeastOnce()) + .closeTunnel( + eq(TEST_APN_NAME), + eq(true), + any(IwlanTunnelCallback.class), + any(IwlanTunnelMetricsImpl.class), + eq(EpdgTunnelManager.BRINGDOWN_REASON_ENABLE_N1_MODE)); + + updatePreferredNetworkType(NETWORK_TYPE_BITMASK_LTE); + + verify(mMockEpdgTunnelManager, atLeastOnce()) + .closeTunnel( + eq(TEST_APN_NAME), + eq(true), + any(IwlanTunnelCallback.class), + any(IwlanTunnelMetricsImpl.class), + eq(EpdgTunnelManager.BRINGDOWN_REASON_DISABLE_N1_MODE)); + } + + @Test + public void testMultipleAllowedNetworkTypeChangeInCall_preferenceChanged_updateAfterCallEnds() + throws Exception { + when(mFakeFeatureFlags.updateN1ModeOnUiChange()).thenReturn(true); + mockCarrierConfigForN1Mode(true); + mockCallState(CALL_STATE_RINGING); + mockSetupDataCallWithPduSessionId(0); + updatePreferredNetworkType(NETWORK_TYPE_BITMASK_NR); + updatePreferredNetworkType(NETWORK_TYPE_BITMASK_LTE); + updatePreferredNetworkType(NETWORK_TYPE_BITMASK_NR); + + verify(mMockEpdgTunnelManager, never()) + .closeTunnel(any(), anyBoolean(), any(), any(), anyInt()); + + // in idle call state + mIwlanDataService + .mIwlanDataServiceHandler + .obtainMessage( + IwlanEventListener.CALL_STATE_CHANGED_EVENT, + DEFAULT_SLOT_INDEX, + CALL_STATE_IDLE) + .sendToTarget(); + mTestLooper.dispatchAll(); + + verify(mMockEpdgTunnelManager, atLeastOnce()) + .closeTunnel( + eq(TEST_APN_NAME), + eq(true), + any(IwlanTunnelCallback.class), + any(IwlanTunnelMetricsImpl.class), + eq(EpdgTunnelManager.BRINGDOWN_REASON_ENABLE_N1_MODE)); + } + + @Test + public void testMultipleAllowedNetworkTypeChangeInCall_preferenceNotChanged_noUpdate() + throws Exception { + when(mFakeFeatureFlags.updateN1ModeOnUiChange()).thenReturn(true); + mockCarrierConfigForN1Mode(true); + mockCallState(CALL_STATE_RINGING); + mockSetupDataCallWithPduSessionId(0); + updatePreferredNetworkType(NETWORK_TYPE_BITMASK_NR); + updatePreferredNetworkType(NETWORK_TYPE_BITMASK_LTE); + + verify(mMockEpdgTunnelManager, never()) + .closeTunnel(any(), anyBoolean(), any(), any(), anyInt()); + + // in idle call state + mIwlanDataService + .mIwlanDataServiceHandler + .obtainMessage( + IwlanEventListener.CALL_STATE_CHANGED_EVENT, + DEFAULT_SLOT_INDEX, + CALL_STATE_IDLE) + .sendToTarget(); + mTestLooper.dispatchAll(); + + verify(mMockEpdgTunnelManager, never()) + .closeTunnel(any(), anyBoolean(), any(), any(), anyInt()); + } + + @Test + public void testOnAllowedNetworkTypeChange_flagDisabled_noTunnelClose() { + when(mFakeFeatureFlags.updateN1ModeOnUiChange()).thenReturn(false); + mockCarrierConfigForN1Mode(true); + mockCallState(CALL_STATE_IDLE); + mockSetupDataCallWithPduSessionId(0); + updatePreferredNetworkType(NETWORK_TYPE_BITMASK_NR); + + verify(mMockEpdgTunnelManager, never()) + .closeTunnel(any(), anyBoolean(), any(), any(), anyInt()); + } + + @Test + public void testOnAllowedNetworkTypeChange_n1ModeNotSupported_noTunnelClose() { + when(mFakeFeatureFlags.updateN1ModeOnUiChange()).thenReturn(true); + mockCarrierConfigForN1Mode(false); + mockCallState(CALL_STATE_IDLE); + mockSetupDataCallWithPduSessionId(0); + updatePreferredNetworkType(NETWORK_TYPE_BITMASK_NR); + + verify(mMockEpdgTunnelManager, never()) + .closeTunnel(any(), anyBoolean(), any(), any(), anyInt()); + } + + @Test + public void testN1ModeNotSupported_tunnelBringupWithNoN1ModeCapability() { + when(mFakeFeatureFlags.updateN1ModeOnUiChange()).thenReturn(true); + mockCarrierConfigForN1Mode(false); + mockSetupDataCallWithPduSessionId(1); + + ArgumentCaptor<TunnelSetupRequest> tunnelSetupRequestCaptor = + ArgumentCaptor.forClass(TunnelSetupRequest.class); + verify(mMockEpdgTunnelManager, times(1)) + .bringUpTunnel(tunnelSetupRequestCaptor.capture(), any(), any()); + TunnelSetupRequest tunnelSetupRequest = tunnelSetupRequestCaptor.getValue(); + assertEquals(PDU_SESSION_ID_UNSET, tunnelSetupRequest.getPduSessionId()); + } + + @Test + public void testNoN1ModeCapabilityInOngoingDataCall_newTunnelBringup_doNotIncludeN1() { + when(mFakeFeatureFlags.updateN1ModeOnUiChange()).thenReturn(true); + mockCarrierConfigForN1Mode(true); + mockSetupDataCallWithPduSessionId(0); + + ArgumentCaptor<TunnelSetupRequest> tunnelSetupRequestCaptor = + ArgumentCaptor.forClass(TunnelSetupRequest.class); + verify(mMockEpdgTunnelManager, times(1)) + .bringUpTunnel(tunnelSetupRequestCaptor.capture(), any(), any()); + TunnelSetupRequest tunnelSetupRequest = tunnelSetupRequestCaptor.getValue(); + assertEquals(PDU_SESSION_ID_UNSET, tunnelSetupRequest.getPduSessionId()); + + updatePreferredNetworkType(NETWORK_TYPE_BITMASK_NR); + mockSetupDataCallWithPduSessionId(1); + + verify(mMockEpdgTunnelManager, times(1)) + .bringUpTunnel(tunnelSetupRequestCaptor.capture(), any(), any()); + tunnelSetupRequest = tunnelSetupRequestCaptor.getValue(); + assertEquals(PDU_SESSION_ID_UNSET, tunnelSetupRequest.getPduSessionId()); + } + + private void mockSetupDataCallWithPduSessionId(int pduSessionId) { + DataProfile dp = buildImsDataProfile(); + onSystemDefaultNetworkConnected( + mMockNetwork, mLinkProperties, TRANSPORT_WIFI, INVALID_SUB_INDEX); + mSpyIwlanDataServiceProvider.setupDataCall( + AccessNetworkType.IWLAN, /* AccessNetworkType */ + dp, /* dataProfile */ + false, /* isRoaming */ + false, /* allowRoaming */ + DataService.REQUEST_REASON_NORMAL, /* DataService.REQUEST_REASON_NORMAL */ + null, /* LinkProperties */ + pduSessionId, /* pduSessionId */ + null, /* sliceInfo */ + null, /* trafficDescriptor */ + true, /* matchAllRuleAllowed */ + mMockDataServiceCallback); + mTestLooper.dispatchAll(); + + verify(mMockEpdgTunnelManager, times(1)) + .bringUpTunnel( + any(TunnelSetupRequest.class), + any(IwlanTunnelCallback.class), + any(IwlanTunnelMetricsImpl.class)); + } } diff --git a/test/com/google/android/iwlan/IwlanEventListenerTest.java b/test/com/google/android/iwlan/IwlanEventListenerTest.java index 1552c97..a922a10 100644 --- a/test/com/google/android/iwlan/IwlanEventListenerTest.java +++ b/test/com/google/android/iwlan/IwlanEventListenerTest.java @@ -18,8 +18,17 @@ package com.google.android.iwlan; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; -import static org.mockito.Mockito.*; - +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.annotation.SuppressLint; import android.content.ContentResolver; import android.content.Context; import android.net.Uri; @@ -35,6 +44,8 @@ import android.telephony.TelephonyManager; import android.telephony.ims.ImsManager; import android.telephony.ims.ImsMmTelManager; +import com.google.android.iwlan.flags.FeatureFlags; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -42,7 +53,8 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.MockitoSession; -import java.util.*; +import java.util.ArrayList; +import java.util.List; public class IwlanEventListenerTest { private static final String TAG = "IwlanEventListenerTest"; @@ -59,6 +71,7 @@ public class IwlanEventListenerTest { @Mock private ImsManager mMockImsManager; @Mock private ImsMmTelManager mMockImsMmTelManager; @Mock private TelephonyManager mMockTelephonyManager; + @Mock private FeatureFlags mFakeFeatureFlags; private static final int DEFAULT_SLOT_INDEX = 0; private static final int OTHER_SLOT_INDEX = 1; @@ -70,6 +83,7 @@ public class IwlanEventListenerTest { Uri.parse("content://telephony/siminfo/cross_sim_calling_enabled/2"); private static final Uri WFC_ENABLED_URI = Uri.parse("content://telephony/siminfo/wfc/2"); private IwlanEventListener mIwlanEventListener; + private IwlanEventListener mSpyIwlanEventListener; private List<Integer> events; MockitoSession mStaticMockSession; @@ -111,6 +125,8 @@ public class IwlanEventListenerTest { IwlanEventListener.resetAllInstances(); mIwlanEventListener = IwlanEventListener.getInstance(mMockContext, DEFAULT_SLOT_INDEX); + mSpyIwlanEventListener = + spy(new IwlanEventListener(mMockContext, DEFAULT_SLOT_INDEX, mFakeFeatureFlags)); } @After @@ -348,4 +364,54 @@ public class IwlanEventListenerTest { mIwlanEventListener.notifyCurrentSetting(WFC_ENABLED_URI); verify(mMockMessage, times(1)).sendToTarget(); } + + @SuppressLint("MissingPermission") + @Test + public void testDisable5gViaUi() throws Exception { + when(mFakeFeatureFlags.updateN1ModeOnUiChange()).thenReturn(true); + + when(mMockHandler.obtainMessage( + eq(IwlanEventListener.PREFERRED_NETWORK_TYPE_CHANGED_EVENT), + eq(DEFAULT_SLOT_INDEX), + anyInt(), + eq(TelephonyManager.NETWORK_TYPE_BITMASK_LTE))) + .thenReturn(mMockMessage); + + events = new ArrayList<>(); + events.add(IwlanEventListener.PREFERRED_NETWORK_TYPE_CHANGED_EVENT); + mIwlanEventListener.addEventListener(events, mMockHandler); + mSpyIwlanEventListener.registerTelephonyCallback(); + TelephonyCallback.AllowedNetworkTypesListener mTelephonyCallback = + mSpyIwlanEventListener.getTelephonyCallback(); + + mTelephonyCallback.onAllowedNetworkTypesChanged( + TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER, + TelephonyManager.NETWORK_TYPE_BITMASK_LTE); + verify(mMockMessage, times(1)).sendToTarget(); + } + + @SuppressLint("MissingPermission") + @Test + public void testEnable5gViaUi() throws Exception { + when(mFakeFeatureFlags.updateN1ModeOnUiChange()).thenReturn(true); + + when(mMockHandler.obtainMessage( + eq(IwlanEventListener.PREFERRED_NETWORK_TYPE_CHANGED_EVENT), + eq(DEFAULT_SLOT_INDEX), + anyInt(), + eq(TelephonyManager.NETWORK_TYPE_BITMASK_NR))) + .thenReturn(mMockMessage); + + events = new ArrayList<>(); + events.add(IwlanEventListener.PREFERRED_NETWORK_TYPE_CHANGED_EVENT); + mIwlanEventListener.addEventListener(events, mMockHandler); + mSpyIwlanEventListener.registerTelephonyCallback(); + TelephonyCallback.AllowedNetworkTypesListener mTelephonyCallback = + mSpyIwlanEventListener.getTelephonyCallback(); + + mTelephonyCallback.onAllowedNetworkTypesChanged( + TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER, + TelephonyManager.NETWORK_TYPE_BITMASK_NR); + verify(mMockMessage, times(1)).sendToTarget(); + } } diff --git a/test/com/google/android/iwlan/epdg/EpdgSelectorTest.java b/test/com/google/android/iwlan/epdg/EpdgSelectorTest.java index 9727893..d328816 100644 --- a/test/com/google/android/iwlan/epdg/EpdgSelectorTest.java +++ b/test/com/google/android/iwlan/epdg/EpdgSelectorTest.java @@ -52,6 +52,7 @@ import android.util.Log; import com.google.android.iwlan.ErrorPolicyManager; import com.google.android.iwlan.IwlanError; +import com.google.android.iwlan.flags.FeatureFlags; import org.junit.After; import org.junit.Before; @@ -70,8 +71,6 @@ import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; -import com.google.android.iwlan.flags.FeatureFlags; - public class EpdgSelectorTest { private static final String TAG = "EpdgSelectorTest"; @@ -747,6 +746,181 @@ public class EpdgSelectorTest { assertEquals(InetAddress.getByName(TEST_IP_ADDRESS_2), testInetAddresses.get(2)); } + @Test + public void testTemporaryExcludedIpAddressWhenDisabledExcludeFailedIp() throws Exception { + doReturn(false).when(mfakeFeatureFlags).epdgSelectionExcludeFailedIpAddress(); + when(DnsResolver.getInstance()).thenReturn(mMockDnsResolver); + doReturn(true).when(mEpdgSelector).hasIpv4Address(mMockNetwork); + doReturn(true).when(mEpdgSelector).hasIpv6Address(mMockNetwork); + + String fqdnFromRplmn = "epdg.epc.mnc122.mcc300.pub.3gppnetwork.org"; + final String staticAddr = "epdg.epc.mnc120.mcc300.pub.3gppnetwork.org"; + + when(mMockTelephonyManager.getNetworkOperator()).thenReturn("300122"); + mTestBundle.putStringArray( + CarrierConfigManager.Iwlan.KEY_MCC_MNCS_STRING_ARRAY, new String[] {"300-122"}); + + mTestBundle.putIntArray( + CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY, + new int[] { + CarrierConfigManager.Iwlan.EPDG_ADDRESS_PLMN, + CarrierConfigManager.Iwlan.EPDG_ADDRESS_STATIC + }); + mTestBundle.putIntArray( + CarrierConfigManager.Iwlan.KEY_EPDG_PLMN_PRIORITY_INT_ARRAY, + new int[] {CarrierConfigManager.Iwlan.EPDG_PLMN_RPLMN}); + + mTestBundle.putString( + CarrierConfigManager.Iwlan.KEY_EPDG_STATIC_ADDRESS_STRING, staticAddr); + + mFakeDns.setAnswer(fqdnFromRplmn, new String[] {TEST_IP_ADDRESS}, TYPE_A); + mFakeDns.setAnswer(staticAddr, new String[] {TEST_IP_ADDRESS_1, TEST_IPV6_ADDRESS}, TYPE_A); + + ArrayList<InetAddress> testInetAddresses = getValidatedServerListWithDefaultParams(false); + assertArrayEquals( + List.of( + InetAddress.getByName(TEST_IP_ADDRESS), + InetAddress.getByName(TEST_IP_ADDRESS_1), + InetAddress.getByName(TEST_IPV6_ADDRESS)) + .toArray(), + testInetAddresses.toArray()); + + mEpdgSelector.onEpdgConnectionFailed(InetAddress.getByName(TEST_IP_ADDRESS)); + // Flag disabled should not affect the result + testInetAddresses = getValidatedServerListWithDefaultParams(false); + assertArrayEquals( + List.of( + InetAddress.getByName(TEST_IP_ADDRESS), + InetAddress.getByName(TEST_IP_ADDRESS_1), + InetAddress.getByName(TEST_IPV6_ADDRESS)) + .toArray(), + testInetAddresses.toArray()); + + mEpdgSelector.onEpdgConnectedSuccessfully(); + // Flag disabled should not affect the result + testInetAddresses = getValidatedServerListWithDefaultParams(false); + assertArrayEquals( + List.of( + InetAddress.getByName(TEST_IP_ADDRESS), + InetAddress.getByName(TEST_IP_ADDRESS_1), + InetAddress.getByName(TEST_IPV6_ADDRESS)) + .toArray(), + testInetAddresses.toArray()); + } + + @Test + public void testTemporaryExcludedIpAddressWhenEnabledExcludeFailedIp() throws Exception { + doReturn(true).when(mfakeFeatureFlags).epdgSelectionExcludeFailedIpAddress(); + when(DnsResolver.getInstance()).thenReturn(mMockDnsResolver); + doReturn(true).when(mEpdgSelector).hasIpv4Address(mMockNetwork); + doReturn(true).when(mEpdgSelector).hasIpv6Address(mMockNetwork); + + final String fqdnFromRplmn = "epdg.epc.mnc122.mcc300.pub.3gppnetwork.org"; + final String staticAddr = "epdg.epc.mnc120.mcc300.pub.3gppnetwork.org"; + + when(mMockTelephonyManager.getNetworkOperator()).thenReturn("300122"); + mTestBundle.putStringArray( + CarrierConfigManager.Iwlan.KEY_MCC_MNCS_STRING_ARRAY, new String[] {"300-122"}); + + mTestBundle.putIntArray( + CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY, + new int[] { + CarrierConfigManager.Iwlan.EPDG_ADDRESS_PLMN, + CarrierConfigManager.Iwlan.EPDG_ADDRESS_STATIC + }); + mTestBundle.putIntArray( + CarrierConfigManager.Iwlan.KEY_EPDG_PLMN_PRIORITY_INT_ARRAY, + new int[] {CarrierConfigManager.Iwlan.EPDG_PLMN_RPLMN}); + + mTestBundle.putString( + CarrierConfigManager.Iwlan.KEY_EPDG_STATIC_ADDRESS_STRING, staticAddr); + + mFakeDns.setAnswer(fqdnFromRplmn, new String[] {TEST_IP_ADDRESS}, TYPE_A); + mFakeDns.setAnswer(staticAddr, new String[] {TEST_IP_ADDRESS_1, TEST_IPV6_ADDRESS}, TYPE_A); + + ArrayList<InetAddress> testInetAddresses = getValidatedServerListWithDefaultParams(false); + assertArrayEquals( + List.of( + InetAddress.getByName(TEST_IP_ADDRESS), + InetAddress.getByName(TEST_IP_ADDRESS_1), + InetAddress.getByName(TEST_IPV6_ADDRESS)) + .toArray(), + testInetAddresses.toArray()); + + mEpdgSelector.onEpdgConnectionFailed(InetAddress.getByName(TEST_IP_ADDRESS)); + testInetAddresses = getValidatedServerListWithDefaultParams(false); + assertArrayEquals( + List.of( + InetAddress.getByName(TEST_IP_ADDRESS_1), + InetAddress.getByName(TEST_IPV6_ADDRESS)) + .toArray(), + testInetAddresses.toArray()); + + // Reset temporary excluded ip addresses + mEpdgSelector.onEpdgConnectedSuccessfully(); + testInetAddresses = getValidatedServerListWithDefaultParams(false); + assertArrayEquals( + List.of( + InetAddress.getByName(TEST_IP_ADDRESS), + InetAddress.getByName(TEST_IP_ADDRESS_1), + InetAddress.getByName(TEST_IPV6_ADDRESS)) + .toArray(), + testInetAddresses.toArray()); + + mEpdgSelector.onEpdgConnectionFailed(InetAddress.getByName(TEST_IP_ADDRESS)); + testInetAddresses = getValidatedServerListWithDefaultParams(false); + assertArrayEquals( + List.of( + InetAddress.getByName(TEST_IP_ADDRESS_1), + InetAddress.getByName(TEST_IPV6_ADDRESS)) + .toArray(), + testInetAddresses.toArray()); + + mEpdgSelector.onEpdgConnectionFailed(InetAddress.getByName(TEST_IPV6_ADDRESS)); + testInetAddresses = getValidatedServerListWithDefaultParams(false); + assertArrayEquals( + List.of(InetAddress.getByName(TEST_IP_ADDRESS_1)).toArray(), + testInetAddresses.toArray()); + + mEpdgSelector.onEpdgConnectionFailed(InetAddress.getByName(TEST_IP_ADDRESS_1)); + // All ip addresses removed, should reset excluded address + testInetAddresses = getValidatedServerListWithDefaultParams(false); + assertArrayEquals( + List.of( + InetAddress.getByName(TEST_IP_ADDRESS), + InetAddress.getByName(TEST_IP_ADDRESS_1), + InetAddress.getByName(TEST_IPV6_ADDRESS)) + .toArray(), + testInetAddresses.toArray()); + + mEpdgSelector.onEpdgConnectionFailed(InetAddress.getByName(TEST_IP_ADDRESS_1)); + testInetAddresses = getValidatedServerListWithDefaultParams(false); + assertArrayEquals( + List.of( + InetAddress.getByName(TEST_IP_ADDRESS), + InetAddress.getByName(TEST_IPV6_ADDRESS)) + .toArray(), + testInetAddresses.toArray()); + + // When the original result changed + mFakeDns.setAnswer(staticAddr, new String[] {TEST_IP_ADDRESS_1}, TYPE_A); + mFakeDns.setAnswer(fqdnFromRplmn, new String[] {TEST_IP_ADDRESS_3}, TYPE_A); + testInetAddresses = getValidatedServerListWithDefaultParams(false); + assertArrayEquals( + List.of(InetAddress.getByName(TEST_IP_ADDRESS_3)).toArray(), + testInetAddresses.toArray()); + + mEpdgSelector.onEpdgConnectionFailed(InetAddress.getByName(TEST_IP_ADDRESS_3)); + // It should also reset the excluded list once all ip addresses are excluded + testInetAddresses = getValidatedServerListWithDefaultParams(false); + assertArrayEquals( + List.of( + InetAddress.getByName(TEST_IP_ADDRESS_3), + InetAddress.getByName(TEST_IP_ADDRESS_1)) + .toArray(), + testInetAddresses.toArray()); + } + private void setAnswerForCellularMethod(boolean isEmergency, int mcc, int mnc) throws Exception { String expectedFqdn1 = diff --git a/test/com/google/android/iwlan/epdg/EpdgTunnelManagerTest.java b/test/com/google/android/iwlan/epdg/EpdgTunnelManagerTest.java index de53193..ff413b5 100644 --- a/test/com/google/android/iwlan/epdg/EpdgTunnelManagerTest.java +++ b/test/com/google/android/iwlan/epdg/EpdgTunnelManagerTest.java @@ -176,6 +176,7 @@ public class EpdgTunnelManagerTest { public void setUp() throws Exception { EpdgTunnelManager.resetAllInstances(); when(mMockContext.getSystemService(eq(IpSecManager.class))).thenReturn(mMockIpSecManager); + when(mFakeFeatureFlags.epdgSelectionExcludeFailedIpAddress()).thenReturn(false); mEpdgTunnelManager = spy(new EpdgTunnelManager(mMockContext, DEFAULT_SLOT_INDEX, mFakeFeatureFlags)); @@ -1083,6 +1084,59 @@ public class EpdgTunnelManagerTest { verify(mMockIwlanTunnelCallback, times(1)).onClosed(eq(testApnName), eq(error)); } + @Test + public void testGetValidEpdgAddress_WhenExcludeFailedIpEnabled() throws Exception { + String testApnName = "www.xyz.com"; + when(mFakeFeatureFlags.epdgSelectionExcludeFailedIpAddress()).thenReturn(true); + + List<InetAddress> ipList1 = + List.of(InetAddress.getByName("1.1.1.1"), InetAddress.getByName("8.8.8.8")); + mEpdgTunnelManager.validateAndSetEpdgAddress(ipList1); + + IwlanError error = new IwlanError(new IkeInternalException(new IOException())); + + doReturn(0L).when(mEpdgTunnelManager).reportIwlanError(eq(testApnName), eq(error)); + setupMockForGetConfig(null); + + doReturn(null) + .doReturn(null) + .when(mMockIkeSessionCreator) + .createIkeSession( + eq(mMockContext), + any(IkeSessionParams.class), + any(ChildSessionParams.class), + any(Executor.class), + any(IkeSessionCallback.class), + any(ChildSessionCallback.class)); + doReturn(true).when(mEpdgTunnelManager).canBringUpTunnel(eq(testApnName)); + + boolean ret = + mEpdgTunnelManager.bringUpTunnel( + getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP), + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics); + assertTrue(ret); + mTestLooper.dispatchAll(); + + List<InetAddress> ipList2 = + List.of(InetAddress.getByName("1.1.1.1"), InetAddress.getByName("8.8.8.8")); + mEpdgTunnelManager.sendSelectionRequestComplete( + ipList2, new IwlanError(IwlanError.NO_ERROR), 1); + mTestLooper.dispatchAll(); + + // When exclude failed IP is enabled, EpdgSelector is responsible to excluding the failed + // IP address from result. EpdgTunnelManager should always use the first IP address from + // the ePDG selection result IP address list, regardless the list is same as prev or not + EpdgTunnelManager.TmIkeSessionCallback ikeSessionCallback = + verifyCreateIkeSession(ipList2.get(0)); + ikeSessionCallback.onClosedWithException( + new IkeInternalException(new IOException("Retransmitting failure"))); + mTestLooper.dispatchAll(); + + verify(mEpdgTunnelManager, times(1)).reportIwlanError(eq(testApnName), eq(error)); + verify(mMockIwlanTunnelCallback, times(1)).onClosed(eq(testApnName), eq(error)); + } + private EpdgTunnelManager.TmIkeSessionCallback verifyCreateIkeSession(InetAddress ip) throws Exception { ArgumentCaptor<IkeSessionParams> ikeSessionParamsCaptor = @@ -1506,6 +1560,8 @@ public class EpdgTunnelManagerTest { ChildSessionCallback childSessionCallback = ikeSessionArgumentCaptors.mChildSessionCallbackCaptor.getValue(); verifyTunnelOnOpened(toBeOpenedApnName, childSessionCallback); + verify(mMockEpdgSelector, times(0)).onEpdgConnectionFailed(any()); + verify(mMockEpdgSelector).onEpdgConnectedSuccessfully(); } @Test @@ -1811,23 +1867,13 @@ public class EpdgTunnelManagerTest { } @Test - public void testEnableN1modeCapabilityWithValidPduSessionId_nonInclusion() throws Exception { - verifyN1modeCapability(0, false); - } - - @Test - public void testDisableN1modeCapabilityWithInvalidPduSessionId_nonInclusion() throws Exception { - verifyN1modeCapability(0, false); + public void testUnsetPduSessionIdInclusion() throws Exception { + verifyN1modeCapability(0); } @Test - public void testEnableN1modeCapabilityWithValidPduSessionId_inclusion() throws Exception { - verifyN1modeCapability(8, true); - } - - @Test - public void testDisableN1modeCapabilityWithValidPduSessionId_nonInclusion() throws Exception { - verifyN1modeCapability(8, false); + public void testPduSessionIdInclusion() throws Exception { + verifyN1modeCapability(8); } @Test @@ -1936,9 +1982,7 @@ public class EpdgTunnelManagerTest { verify(mMockIwlanTunnelCallback, times(1)).onClosed(eq(testApnName), eq(error)); } - private void verifyN1modeCapability(int pduSessionId, boolean isN1ModeSupported) - throws Exception { - doReturn(isN1ModeSupported).when(mEpdgTunnelManager).isN1ModeSupported(); + private void verifyN1modeCapability(int pduSessionId) throws Exception { String testApnName = "www.xyz.com"; byte pduSessionIdToByte = (byte) pduSessionId; @@ -1992,11 +2036,7 @@ public class EpdgTunnelManagerTest { byte pduSessionIdByte = ikeSessionParams.getIke3gppExtension().getIke3gppParams().getPduSessionId(); - if (isN1ModeSupported && pduSessionId != 0) { - assertEquals(pduSessionIdToByte, pduSessionIdByte); - } else { - assertEquals(0, pduSessionIdByte); - } + assertEquals(pduSessionIdToByte, pduSessionIdByte); } @Test @@ -2526,6 +2566,8 @@ public class EpdgTunnelManagerTest { mTestLooper.dispatchAll(); verify(mEpdgTunnelManager, times(1)).reportIwlanError(eq(testApnName), eq(error)); + verify(mMockEpdgSelector).onEpdgConnectionFailed(eq(EXPECTED_EPDG_ADDRESSES.get(0))); + verify(mMockEpdgSelector, times(0)).onEpdgConnectedSuccessfully(); verify(mMockIwlanTunnelCallback, atLeastOnce()).onClosed(eq(testApnName), eq(error)); } @@ -2574,33 +2616,6 @@ public class EpdgTunnelManagerTest { verify(mMockIwlanTunnelCallback, times(1)).onClosed(eq(TEST_APN_NAME), eq(error)); } - private boolean verifyIsN1ModeSupported(int[] nrAvailability) { - PersistableBundle bundle = new PersistableBundle(); - bundle.putIntArray( - CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, nrAvailability); - - setupMockForGetConfig(bundle); - - return mEpdgTunnelManager.isN1ModeSupported(); - } - - @Test - public void testIsN1ModeSupportedTrue() throws Exception { - assertTrue( - verifyIsN1ModeSupported( - new int[] { - CarrierConfigManager.CARRIER_NR_AVAILABILITY_NSA, - CarrierConfigManager.CARRIER_NR_AVAILABILITY_SA - })); - } - - @Test - public void testIsN1ModeSupportedFalse() throws Exception { - assertFalse( - verifyIsN1ModeSupported( - new int[] {CarrierConfigManager.CARRIER_NR_AVAILABILITY_NSA})); - } - @Test public void testUpdateNetworkToOpenedTunnel() throws Exception { String apnName = "ims"; |