diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-07-07 04:50:42 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-07-07 04:50:42 +0000 |
commit | 9d7ee7aeb092ebebbbe7db8994f55656e0ebc024 (patch) | |
tree | cd1307fa58b8f58ed944e68eccffbd8e63dc6225 | |
parent | de24857d2f0220f7e1d042ccaa41aff36fa43bdf (diff) | |
parent | 26c3fa06288d0c97a3117f839a3360f3fce845f6 (diff) | |
download | Iwlan-android14-mainline-appsearch-release.tar.gz |
Snap for 10453563 from 26c3fa06288d0c97a3117f839a3360f3fce845f6 to mainline-appsearch-releaseaml_ase_341510000aml_ase_341410000aml_ase_341310010aml_ase_341113000aml_ase_340913000android14-mainline-appsearch-release
Change-Id: I2316e34f5cd26115fe4c9d7914bffe545ba0b605
26 files changed, 7053 insertions, 2402 deletions
@@ -2,12 +2,21 @@ package { default_applicable_licenses: ["Android-Apache-2.0"], } +genrule { + name: "statslog-Iwlan-java-gen", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --java $(out) --module iwlan --javaPackage com.google.android.iwlan" + + " --javaClass IwlanStatsLog", + out: ["com/google/android/iwlan/IwlanStatsLog.java"], +} + android_app { name: "Iwlan", manifest: "AndroidManifest.xml", srcs: [ "src/**/*.java", "src/**/I*.aidl", + ":statslog-Iwlan-java-gen", ], resource_dirs: [ "res", @@ -53,6 +62,7 @@ android_test { srcs: [ "src/**/*.java", "test/**/*.java", + ":statslog-Iwlan-java-gen", ], platform_apis: true, diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 6a7a2d4..e0b0b3a 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -8,19 +8,14 @@ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.INTERNET"/> + <uses-permission android:name="android.permission.MANAGE_IPSEC_TUNNELS" /> + <uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" /> - <protected-broadcast android:name="IkeAlarmReceiver.ACTION_DELETE_CHILD" /> - <protected-broadcast android:name="IkeAlarmReceiver.ACTION_REKEY_CHILD" /> - <protected-broadcast android:name="IkeAlarmReceiver.ACTION_DELETE_IKE" /> - <protected-broadcast android:name="IkeAlarmReceiver.ACTION_REKEY_IKE" /> - <protected-broadcast android:name="IkeAlarmReceiver.ACTION_DPD" /> - <protected-broadcast android:name="IkeAlarmReceiver.ACTION_KEEPALIVE" /> - <application android:directBootAware="true" android:defaultToDeviceProtectedStorage="true"> diff --git a/assets/defaultiwlanerrorconfig.json b/assets/defaultiwlanerrorconfig.json index eae1ae2..2d2be95 100644 --- a/assets/defaultiwlanerrorconfig.json +++ b/assets/defaultiwlanerrorconfig.json @@ -50,6 +50,10 @@ # 3.4.3. APM_ENABLE_EVENT: APM off to on toggle. # 3.4.4. WIFI_AP_CHANGED_EVENT: Wifi is connected to a AP with different SSID. # 3.4.5. WIFI_CALLING_DISABLE_EVENT: Wifi calling button on to off toggle. +# 3.5. "HandoverAttemptCount": Integer to specify the number of handover request attempts before +# using initial attach instead. It is an optional field. +# Note: This should only be defined in the config when handover attempt count is enabled and +# "ErrorType" is explicitly defined as "IKE_PROTOCOL_ERROR_TYPE". # # Note: When the value is "*" for any of "ApnName" or "ErrorType" or "ErrorDetails", # it means that the config definition applies to rest of the errors for which @@ -64,20 +68,27 @@ { "ErrorType": "*", "ErrorDetails": ["*"], - "RetryArray": ["5", "10", "-1"], + "RetryArray": ["1", "2", "2", "10", "20", "40", "80", "160", "320", "640", "1280", "1800", "3600", "-1"], "UnthrottlingEvents": ["APM_ENABLE_EVENT", "APM_DISABLE_EVENT", "WIFI_DISABLE_EVENT", "WIFI_AP_CHANGED_EVENT"] }, { "ErrorType": "GENERIC_ERROR_TYPE", "ErrorDetails": ["IO_EXCEPTION"], - "RetryArray": ["0", "0", "0", "60+r15", "120", "-1"], + "RetryArray": ["0", "0", "0", "30", "60+r15", "120", "-1"], "UnthrottlingEvents": ["APM_ENABLE_EVENT", "APM_DISABLE_EVENT", "WIFI_DISABLE_EVENT", "WIFI_AP_CHANGED_EVENT"] }, { "ErrorType": "IKE_PROTOCOL_ERROR_TYPE", - "ErrorDetails": ["24"], - "RetryArray": ["10", "20", "40", "80", "160"], + "ErrorDetails": ["*"], + "RetryArray": ["5", "10", "10", "20", "40", "80", "160", "320", "640", "1280", "1800", "3600", "-1"], "UnthrottlingEvents": ["APM_ENABLE_EVENT", "WIFI_DISABLE_EVENT", "WIFI_CALLING_DISABLE_EVENT"] + }, + { + "ErrorType": "IKE_PROTOCOL_ERROR_TYPE", + "ErrorDetails": ["36"], + "RetryArray": ["0", "0", "0", "10", "20", "40", "80", "160", "320", "640", "1280", "1800", "3600", "-1"], + "UnthrottlingEvents": ["APM_ENABLE_EVENT", "WIFI_DISABLE_EVENT", "WIFI_CALLING_DISABLE_EVENT"], + "HandoverAttemptCount": 3 } ] } diff --git a/com.google.android.iwlan.xml b/com.google.android.iwlan.xml index 467a2c9..f91bb8a 100644 --- a/com.google.android.iwlan.xml +++ b/com.google.android.iwlan.xml @@ -2,5 +2,7 @@ <permissions> <privapp-permissions package="com.google.android.iwlan"> <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> + <permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/> + <permission name="android.permission.SCHEDULE_EXACT_ALARM"/> </privapp-permissions> </permissions> diff --git a/src/com/google/android/iwlan/ErrorPolicyManager.java b/src/com/google/android/iwlan/ErrorPolicyManager.java index 59fad64..88e9a7f 100644 --- a/src/com/google/android/iwlan/ErrorPolicyManager.java +++ b/src/com/google/android/iwlan/ErrorPolicyManager.java @@ -32,12 +32,13 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.google.auto.value.AutoValue; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedReader; -import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -50,6 +51,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; @@ -70,8 +72,8 @@ public class ErrorPolicyManager { /** * This value represents rest of the errors that are not defined above. ErrorDetails should - * mention the specific error. If it doesn't not - the policy will be used as a fallback global - * policy. Currently Supported ErrorDetails "IO_EXCEPTION" "TIMEOUT_EXCEPTION" + * mention the specific error. If it doesn't - the policy will be used as a fallback global + * policy. Currently, Supported ErrorDetails "IO_EXCEPTION" "TIMEOUT_EXCEPTION" * "SERVER_SELECTION_FAILED" "TUNNEL_TRANSFORM_FAILED" */ private static final int GENERIC_ERROR_TYPE = 2; @@ -86,8 +88,13 @@ public class ErrorPolicyManager { */ private static final int IKE_PROTOCOL_ERROR_TYPE = 3; + static ErrorPolicy.Builder builder() { + return new AutoValue_ErrorPolicyManager_ErrorPolicy.Builder() + .setInfiniteRetriesWithLastRetryTime(false); + } + @IntDef({UNKNOWN_ERROR_TYPE, FALLBACK_ERROR_TYPE, GENERIC_ERROR_TYPE, IKE_PROTOCOL_ERROR_TYPE}) - @interface ErrorPolicyErrorType {}; + @interface ErrorPolicyErrorType {} private static final String[] GENERIC_ERROR_DETAIL_STRINGS = { "*", @@ -116,31 +123,14 @@ public class ErrorPolicyManager { private static final int IKE_PROTOCOL_ERROR_PLMN_NOT_ALLOWED = 11011; private static final int IKE_PROTOCOL_ERROR_UNAUTHENTICATED_EMERGENCY_NOT_SUPPORTED = 11055; - @IntDef({ - IKE_PROTOCOL_ERROR_PDN_CONNECTION_REJECTION, - IKE_PROTOCOL_ERROR_MAX_CONNECTION_REACHED, - IKE_PROTOCOL_ERROR_SEMANTIC_ERROR_IN_THE_TFT_OPERATION, - IKE_PROTOCOL_ERROR_SYNTACTICAL_ERROR_IN_THE_TFT_OPERATION, - IKE_PROTOCOL_ERROR_SEMANTIC_ERRORS_IN_PACKET_FILTERS, - IKE_PROTOCOL_ERROR_SYNTACTICAL_ERRORS_IN_PACKET_FILTERS, - IKE_PROTOCOL_ERROR_NON_3GPP_ACCESS_TO_EPC_NOT_ALLOWED, - IKE_PROTOCOL_ERROR_USER_UNKNOWN, - IKE_PROTOCOL_ERROR_NO_APN_SUBSCRIPTION, - IKE_PROTOCOL_ERROR_AUTHORIZATION_REJECTED, - IKE_PROTOCOL_ERROR_ILLEGAL_ME, - IKE_PROTOCOL_ERROR_NETWORK_FAILURE, - IKE_PROTOCOL_ERROR_RAT_TYPE_NOT_ALLOWED, - IKE_PROTOCOL_ERROR_IMEI_NOT_ACCEPTED, - IKE_PROTOCOL_ERROR_PLMN_NOT_ALLOWED, - IKE_PROTOCOL_ERROR_UNAUTHENTICATED_EMERGENCY_NOT_SUPPORTED - }) - @interface IkeProtocolErrorType {}; + /** Private IKEv2 notify message types, as defined in TS 124 502 (section 9.2.4.1) */ + private static final int IKE_PROTOCOL_ERROR_CONGESTION = 15500; private final String LOG_TAG; - private static Map<Integer, ErrorPolicyManager> mInstances = new ConcurrentHashMap<>(); - private Context mContext; - private int mSlotId; + private static final Map<Integer, ErrorPolicyManager> mInstances = new ConcurrentHashMap<>(); + private final Context mContext; + private final int mSlotId; // Policies read from defaultiwlanerrorconfig.json // String APN as key to identify the ErrorPolicies associated with it. @@ -148,15 +138,19 @@ public class ErrorPolicyManager { // Policies read from CarrierConfig // String APN as key to identify the ErrorPolicies associated with it. - private Map<String, List<ErrorPolicy>> mCarrierConfigPolicies = new HashMap<>(); + private final Map<String, List<ErrorPolicy>> mCarrierConfigPolicies = new HashMap<>(); // String APN as key to identify the ErrorInfo associated with that APN - private Map<String, ErrorInfo> mLastErrorForApn = new ConcurrentHashMap<>(); + private final Map<String, ErrorInfo> mLastErrorForApn = new ConcurrentHashMap<>(); + + // Records the most recently reported IwlanError (including NO_ERROR), and the corresponding + // APN. + private ApnWithIwlanError mMostRecentError; // List of current Unthrottling events registered with IwlanEventListener private Set<Integer> mUnthrottlingEvents; - private ErrorStats mErrorStats = new ErrorStats(); + private final ErrorStats mErrorStats = new ErrorStats(); private HandlerThread mHandlerThread; @VisibleForTesting Handler mHandler; @@ -178,11 +172,13 @@ public class ErrorPolicyManager { return mInstances.computeIfAbsent(slotId, k -> new ErrorPolicyManager(context, slotId)); } + @VisibleForTesting + public static void resetAllInstances() { + mInstances.clear(); + } + /** * Release or reset the instance. - * - * @param context - * @param slotId */ public void releaseInstance() { Log.d(LOG_TAG, "Release Instance with slotId: " + mSlotId); @@ -202,6 +198,7 @@ public class ErrorPolicyManager { public synchronized long reportIwlanError(String apn, IwlanError iwlanError) { // Fail by default long retryTime = -1; + mMostRecentError = new ApnWithIwlanError(apn, iwlanError); if (iwlanError.getErrorType() == IwlanError.NO_ERROR) { Log.d(LOG_TAG, "reportIwlanError: NO_ERROR"); @@ -218,7 +215,7 @@ public class ErrorPolicyManager { } if (!mLastErrorForApn.containsKey(apn) || !mLastErrorForApn.get(apn).getError().equals(iwlanError)) { - Log.d(LOG_TAG, "Doesn't match to the previous error" + iwlanError.toString()); + Log.d(LOG_TAG, "Doesn't match to the previous error" + iwlanError); ErrorPolicy policy = findErrorPolicy(apn, iwlanError); ErrorInfo errorInfo = new ErrorInfo(iwlanError, policy); mLastErrorForApn.put(apn, errorInfo); @@ -233,7 +230,7 @@ public class ErrorPolicyManager { * * @param apn apn name for which the error happened * @param iwlanError Error - * @param long backOffTime in seconds + * @param backOffTime in seconds * @return retry time which is the backoff time. -1 if it is NO_ERROR */ public synchronized long reportIwlanError(String apn, IwlanError iwlanError, long backOffTime) { @@ -256,7 +253,7 @@ public class ErrorPolicyManager { retryTime = backOffTime; if (!mLastErrorForApn.containsKey(apn) || !mLastErrorForApn.get(apn).getError().equals(iwlanError)) { - Log.d(LOG_TAG, "Doesn't match to the previous error" + iwlanError.toString()); + Log.d(LOG_TAG, "Doesn't match to the previous error" + iwlanError); ErrorPolicy policy = findErrorPolicy(apn, iwlanError); ErrorInfo errorInfo = new ErrorInfo(iwlanError, policy, backOffTime); mLastErrorForApn.put(apn, errorInfo); @@ -292,28 +289,54 @@ public class ErrorPolicyManager { * @return DataFailCause corresponding to the error for the apn */ public synchronized int getDataFailCause(String apn) { - if (!mLastErrorForApn.containsKey(apn)) { return DataFailCause.NONE; } IwlanError error = mLastErrorForApn.get(apn).getError(); + return getDataFailCause(error); + } + + private int getDataFailCause(IwlanError error) { int ret = DataFailCause.ERROR_UNSPECIFIED; - if (error.getErrorType() == IwlanError.EPDG_SELECTOR_SERVER_SELECTION_FAILED) { + + if (error.getErrorType() == IwlanError.NO_ERROR) { + ret = DataFailCause.NONE; + } else if (error.getErrorType() == IwlanError.EPDG_SELECTOR_SERVER_SELECTION_FAILED) { ret = DataFailCause.IWLAN_DNS_RESOLUTION_NAME_FAILURE; + } else if (error.getErrorType() == IwlanError.EPDG_ADDRESS_ONLY_IPV4_ALLOWED) { + ret = DataFailCause.ONLY_IPV4_ALLOWED; + } else if (error.getErrorType() == IwlanError.EPDG_ADDRESS_ONLY_IPV6_ALLOWED) { + ret = DataFailCause.ONLY_IPV6_ALLOWED; } else if (error.getErrorType() == IwlanError.IKE_INTERNAL_IO_EXCEPTION) { ret = DataFailCause.IWLAN_IKEV2_MSG_TIMEOUT; } else if (error.getErrorType() == IwlanError.SIM_NOT_READY_EXCEPTION) { - ret = DataFailCause.IWLAN_PDN_CONNECTION_REJECTION; - } else if (error.getErrorType() == IwlanError.NETWORK_FAILURE) { - ret = DataFailCause.NETWORK_FAILURE; + ret = DataFailCause.SIM_CARD_CHANGED; + } else if (error.getErrorType() + == IwlanError.IKE_SESSION_CLOSED_BEFORE_CHILD_SESSION_OPENED) { + ret = DataFailCause.IWLAN_IKE_SESSION_CLOSED_BEFORE_CHILD_SESSION_OPENED; + } else if (error.getErrorType() == IwlanError.TUNNEL_NOT_FOUND) { + ret = DataFailCause.IWLAN_TUNNEL_NOT_FOUND; + } else if (error.getErrorType() == IwlanError.IKE_INIT_TIMEOUT) { + ret = DataFailCause.IWLAN_IKE_INIT_TIMEOUT; + } else if (error.getErrorType() == IwlanError.IKE_MOBILITY_TIMEOUT) { + ret = DataFailCause.IWLAN_IKE_MOBILITY_TIMEOUT; + } else if (error.getErrorType() == IwlanError.IKE_DPD_TIMEOUT) { + ret = DataFailCause.IWLAN_IKE_DPD_TIMEOUT; + } else if (error.getErrorType() == IwlanError.TUNNEL_TRANSFORM_FAILED) { + ret = DataFailCause.IWLAN_TUNNEL_TRANSFORM_FAILED; + } else if (error.getErrorType() == IwlanError.IKE_NETWORK_LOST_EXCEPTION) { + ret = DataFailCause.IWLAN_IKE_NETWORK_LOST_EXCEPTION; } else if (error.getErrorType() == IwlanError.IKE_PROTOCOL_EXCEPTION) { Exception exception = error.getException(); - if (exception != null && exception instanceof IkeProtocolException) { + if (exception instanceof IkeProtocolException) { int protocolErrorType = ((IkeProtocolException) exception).getErrorType(); switch (protocolErrorType) { case IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED: ret = DataFailCause.IWLAN_IKEV2_AUTH_FAILURE; break; + case IkeProtocolException.ERROR_TYPE_INTERNAL_ADDRESS_FAILURE: + ret = DataFailCause.IWLAN_EPDG_INTERNAL_ADDRESS_FAILURE; + break; case IKE_PROTOCOL_ERROR_PDN_CONNECTION_REJECTION: ret = DataFailCause.IWLAN_PDN_CONNECTION_REJECTION; break; @@ -362,8 +385,11 @@ public class ErrorPolicyManager { case IKE_PROTOCOL_ERROR_UNAUTHENTICATED_EMERGENCY_NOT_SUPPORTED: ret = DataFailCause.IWLAN_UNAUTHENTICATED_EMERGENCY_NOT_SUPPORTED; break; + case IKE_PROTOCOL_ERROR_CONGESTION: + ret = DataFailCause.IWLAN_CONGESTION; + break; default: - ret = DataFailCause.IWLAN_NETWORK_FAILURE; + ret = DataFailCause.IWLAN_IKE_PRIVATE_PROTOCOL_ERROR; break; } } @@ -371,6 +397,13 @@ public class ErrorPolicyManager { return ret; } + public synchronized int getMostRecentDataFailCause() { + if (mMostRecentError != null) { + return getDataFailCause(mMostRecentError.mIwlanError); + } + return DataFailCause.NONE; + } + /** * Returns the current retryTime based on the lastErrorForApn * @@ -385,6 +418,23 @@ public class ErrorPolicyManager { } /** + * Returns the index of the FQDN to use for ePDG server selection, based on how many FQDNs are + * available, the position of the RetryArray index, and configuration of 'NumAttemptsPerFqdn'. + * + * @param numFqdns number of FQDNs discovered during ePDG server selection. + * @return int index of the FQDN to use for ePDG server selection. -1 (invalid) if RetryArray or + * 'NumAttemptsPerFqdn' is not specified in the ErrorPolicy. + */ + public synchronized int getCurrentFqdnIndex(int numFqdns) { + String apn = mMostRecentError.mApn; + if (!mLastErrorForApn.containsKey(apn)) { + return -1; + } + ErrorInfo errorInfo = mLastErrorForApn.get(apn); + return errorInfo.getCurrentFqdnIndex(numFqdns); + } + + /** * Returns the last error for that apn * * @param apn apn name @@ -397,6 +447,19 @@ public class ErrorPolicyManager { return new IwlanError(IwlanError.NO_ERROR); } + /** + * Returns whether framework should retry tunnel setup with initial PDN bringup request when + * handover request fails. + * + * @param apn apn name + * @return boolean result of whether framework should retry tunnel setup with initial PDN + * bringup request when handover request fails + */ + public synchronized boolean shouldRetryWithInitialAttach(String apn) { + ErrorInfo errorInfo = mLastErrorForApn.get(apn); + return errorInfo != null && errorInfo.shouldRetryWithInitialAttach(); + } + public void logErrorPolicies() { Log.d(LOG_TAG, "mCarrierConfigPolicies:"); for (Map.Entry<String, List<ErrorPolicy>> entry : mCarrierConfigPolicies.entrySet()) { @@ -414,7 +477,7 @@ public class ErrorPolicyManager { } } - public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + public synchronized void dump(PrintWriter pw) { pw.println("---- ErrorPolicyManager ----"); for (Map.Entry<String, ErrorInfo> entry : mLastErrorForApn.entrySet()) { pw.print("APN: " + entry.getKey() + " IwlanError: " + entry.getValue().getError()); @@ -484,14 +547,20 @@ public class ErrorPolicyManager { return selectedPolicy; } - private void initHandler() { + @VisibleForTesting + void initHandler() { + mHandler = new EpmHandler(getLooper()); + } + + @VisibleForTesting + Looper getLooper() { mHandlerThread = new HandlerThread("ErrorPolicyManagerThread"); mHandlerThread.start(); - mHandler = new EpmHandler(mHandlerThread.getLooper()); + return mHandlerThread.getLooper(); } private String getDefaultJSONConfig() throws IOException { - String str = ""; + String str; StringBuilder stringBuilder = new StringBuilder(); InputStream is = mContext.getAssets().open("defaultiwlanerrorconfig.json"); BufferedReader reader = new BufferedReader(new InputStreamReader(is)); @@ -507,7 +576,8 @@ public class ErrorPolicyManager { return stringBuilder.toString(); } - private Map<String, List<ErrorPolicy>> readErrorPolicies(JSONArray apnArray) + @VisibleForTesting + Map<String, List<ErrorPolicy>> readErrorPolicies(JSONArray apnArray) throws JSONException, IllegalArgumentException { Map<String, List<ErrorPolicy>> errorPolicies = new HashMap<>(); for (int i = 0; i < apnArray.length(); i++) { @@ -521,21 +591,47 @@ public class ErrorPolicyManager { String errorTypeStr = ((String) errorTypeObject.get("ErrorType")).trim(); JSONArray errorDetailArray = (JSONArray) errorTypeObject.get("ErrorDetails"); - int errorType = UNKNOWN_ERROR_TYPE; + int errorType; if ((errorType = getErrorPolicyErrorType(errorTypeStr)) == UNKNOWN_ERROR_TYPE) { throw new IllegalArgumentException("Unknown error type in the parsing"); } - ErrorPolicy errorPolicy = - new ErrorPolicy( - errorType, - parseErrorDetails(errorType, errorDetailArray), - parseRetryArray((JSONArray) errorTypeObject.get("RetryArray")), - parseUnthrottlingEvents( - (JSONArray) errorTypeObject.get("UnthrottlingEvents"))); + List<Integer> retryArray = + parseRetryArray((JSONArray) errorTypeObject.get("RetryArray")); + + ErrorPolicy.Builder errorPolicyBuilder = + builder() + .setErrorType(errorType) + .setErrorDetails(parseErrorDetails(errorType, errorDetailArray)) + .setRetryArray(retryArray) + .setUnthrottlingEvents( + parseUnthrottlingEvents( + (JSONArray) + errorTypeObject.get("UnthrottlingEvents"))); + + if (!retryArray.isEmpty() && retryArray.get(retryArray.size() - 1) == -1L) { + errorPolicyBuilder.setInfiniteRetriesWithLastRetryTime(true); + } + + if (errorTypeObject.has("NumAttemptsPerFqdn")) { + errorPolicyBuilder.setNumAttemptsPerFqdn( + errorTypeObject.getInt("NumAttemptsPerFqdn")); + } + + if (errorTypeObject.has("HandoverAttemptCount")) { + if (errorType != IKE_PROTOCOL_ERROR_TYPE) { + throw new IllegalArgumentException( + "Handover attempt count should not be applied when errorType is not" + + " explicitly defined as IKE_PROTOCOL_ERROR_TYPE"); + } + errorPolicyBuilder.setHandoverAttemptCount( + errorTypeObject.getInt("HandoverAttemptCount")); + } + + ErrorPolicy errorPolicy = errorPolicyBuilder.build(); - errorPolicies.putIfAbsent(apnName, new ArrayList<ErrorPolicy>()); + errorPolicies.putIfAbsent(apnName, new ArrayList<>()); errorPolicies.get(apnName).add(errorPolicy); } } @@ -551,7 +647,7 @@ public class ErrorPolicyManager { // catch misplaced -1 retry times in the array. // 1. if it is not placed at the last position in the array // 2. if it is placed in the first position (catches the case where it is - // the only element. + // the only element). if (retryTime.equals("-1") && (i != retryArray.length() - 1 || i == 0)) { throw new IllegalArgumentException("Misplaced -1 in retry array"); } @@ -621,7 +717,7 @@ public class ErrorPolicyManager { boolean ret = true; if (errorDetailStr.contains("-")) { // verify range format - String rangeNumbers[] = errorDetailStr.split("-"); + String[] rangeNumbers = errorDetailStr.split("-"); if (rangeNumbers.length == 2) { for (String range : rangeNumbers) { if (!TextUtils.isDigitsOnly(range)) { @@ -673,13 +769,13 @@ public class ErrorPolicyManager { for (Map.Entry<String, List<ErrorPolicy>> entry : mCarrierConfigPolicies.entrySet()) { List<ErrorPolicy> errorPolicies = entry.getValue(); for (ErrorPolicy errorPolicy : errorPolicies) { - events.addAll(errorPolicy.mUnthrottlingEvents); + events.addAll(errorPolicy.unthrottlingEvents()); } } for (Map.Entry<String, List<ErrorPolicy>> entry : mDefaultPolicies.entrySet()) { List<ErrorPolicy> errorPolicies = entry.getValue(); for (ErrorPolicy errorPolicy : errorPolicies) { - events.addAll(errorPolicy.mUnthrottlingEvents); + events.addAll(errorPolicy.unthrottlingEvents()); } } events.add(IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT); @@ -693,7 +789,7 @@ public class ErrorPolicyManager { */ private synchronized void readFromCarrierConfig(int currentCarrierId) { String carrierConfigErrorPolicy = - (String) IwlanHelper.getConfig(KEY_ERROR_POLICY_CONFIG_STRING, mContext, mSlotId); + IwlanHelper.getConfig(KEY_ERROR_POLICY_CONFIG_STRING, mContext, mSlotId); if (carrierConfigErrorPolicy == null) { Log.e(LOG_TAG, "ErrorPolicy from Carrier Config is NULL"); return; @@ -712,9 +808,7 @@ public class ErrorPolicyManager { LOG_TAG, "Unable to parse the ErrorPolicy from CarrierConfig\n" + carrierConfigErrorPolicy); - if (mCarrierConfigPolicies != null) { - mCarrierConfigPolicies.clear(); - } + mCarrierConfigPolicies.clear(); carrierConfigErrorPolicyString = null; e.printStackTrace(); } @@ -726,18 +820,16 @@ public class ErrorPolicyManager { registerEvents = getAllUnthrottlingEvents(); mUnthrottlingEvents = getAllUnthrottlingEvents(); - if (registerEvents != null && unregisterEvents != null) { + if (unregisterEvents != null) { registerEvents.removeAll(unregisterEvents); unregisterEvents.removeAll(mUnthrottlingEvents); } - if (registerEvents != null) { - IwlanEventListener.getInstance(mContext, mSlotId) - .addEventListener(new ArrayList<Integer>(registerEvents), mHandler); - } + IwlanEventListener.getInstance(mContext, mSlotId) + .addEventListener(new ArrayList<>(registerEvents), mHandler); if (unregisterEvents != null) { IwlanEventListener.getInstance(mContext, mSlotId) - .removeEventListener(new ArrayList<Integer>(unregisterEvents), mHandler); + .removeEventListener(new ArrayList<>(unregisterEvents), mHandler); } Log.d( LOG_TAG, @@ -774,35 +866,55 @@ public class ErrorPolicyManager { return mErrorStats; } - class ErrorPolicy { - @ErrorPolicyErrorType int mErrorType; - List<String> mErrorDetails; - List<Integer> mRetryArray; - List<Integer> mUnthrottlingEvents; + @AutoValue + abstract static class ErrorPolicy { + private static final String LOG_TAG = ErrorPolicyManager.class.getSimpleName(); + + abstract @ErrorPolicyErrorType int errorType(); + + abstract List<String> errorDetails(); + + abstract List<Integer> retryArray(); + + abstract Boolean infiniteRetriesWithLastRetryTime(); + + abstract List<Integer> unthrottlingEvents(); + + abstract Optional<Integer> numAttemptsPerFqdn(); + + abstract Optional<Integer> handoverAttemptCount(); - ErrorPolicy( - @ErrorPolicyErrorType int errorType, - List<String> errorDetails, - List<Integer> retryArray, - List<Integer> unthrottlingEvents) { - mErrorType = errorType; - mErrorDetails = errorDetails; - mRetryArray = retryArray; - mUnthrottlingEvents = unthrottlingEvents; + @AutoValue.Builder + abstract static class Builder { + abstract Builder setErrorType(int errorType); + + abstract Builder setErrorDetails(List<String> errorDetails); + + abstract Builder setRetryArray(List<Integer> retryArray); + + abstract Builder setInfiniteRetriesWithLastRetryTime( + Boolean infiniteRetriesWithLastRetryTime); + + abstract Builder setUnthrottlingEvents(List<Integer> unthrottlingEvents); + + abstract Builder setNumAttemptsPerFqdn(Integer numAttemptsPerFqdn); + + abstract Builder setHandoverAttemptCount(Integer handoverAttemptCount); + + abstract ErrorPolicy build(); } long getRetryTime(int index) { long retryTime = -1; - if (mRetryArray.size() > 0) { + if (retryArray().size() > 0) { // If the index is greater than or equal to the last element's index // and if the last item in the retryArray is "-1" use the retryTime // of the element before the last element to repeat the element. - if (index >= mRetryArray.size() - 1 - && mRetryArray.get(mRetryArray.size() - 1) == -1L) { - index = mRetryArray.size() - 2; + if (infiniteRetriesWithLastRetryTime()) { + index = Math.min(index, retryArray().size() - 2); } - if (index >= 0 && index < mRetryArray.size()) { - retryTime = mRetryArray.get(index); + if (index >= 0 && index < retryArray().size()) { + retryTime = retryArray().get(index); } } @@ -814,25 +926,39 @@ public class ErrorPolicyManager { return retryTime; } + int getCurrentFqdnIndex(int retryIndex, int numFqdns) { + int result = -1; + if (numAttemptsPerFqdn().isEmpty() || retryArray().size() <= 0) { + return result; + } + // Cycles between 0 and (numFqdns - 1), based on the current attempt count and size of + // mRetryArray. + return (retryIndex + 1) / numAttemptsPerFqdn().get() % numFqdns; + } + @ErrorPolicyErrorType int getErrorType() { - return mErrorType; + return errorType(); + } + + int getHandoverAttemptCount() { + return handoverAttemptCount().orElse(Integer.MAX_VALUE); } synchronized boolean canUnthrottle(int event) { - return mUnthrottlingEvents.contains(event); + return unthrottlingEvents().contains(event); } boolean match(IwlanError iwlanError) { // Generic by default to match to generic policy. - String iwlanErrorDetail = "*"; - if (mErrorType == FALLBACK_ERROR_TYPE) { + String iwlanErrorDetail; + if (errorType() == FALLBACK_ERROR_TYPE) { return true; - } else if (mErrorType == IKE_PROTOCOL_ERROR_TYPE + } else if (errorType() == IKE_PROTOCOL_ERROR_TYPE && iwlanError.getErrorType() == IwlanError.IKE_PROTOCOL_EXCEPTION) { IkeProtocolException exception = (IkeProtocolException) iwlanError.getException(); iwlanErrorDetail = String.valueOf(exception.getErrorType()); - } else if (mErrorType == GENERIC_ERROR_TYPE) { + } else if (errorType() == GENERIC_ERROR_TYPE) { iwlanErrorDetail = getGenericErrorDetailString(iwlanError); if (iwlanErrorDetail.equals("UNKNOWN")) { return false; @@ -842,14 +968,14 @@ public class ErrorPolicyManager { } boolean ret = false; - for (String errorDetail : mErrorDetails) { - if (mErrorType == IKE_PROTOCOL_ERROR_TYPE + for (String errorDetail : errorDetails()) { + if (errorType() == IKE_PROTOCOL_ERROR_TYPE && iwlanError.getErrorType() == IwlanError.IKE_PROTOCOL_EXCEPTION && errorDetail.contains("-")) { // error detail is stored in range format. // ErrorPolicyManager#verifyIkeProtocolErrorDetail will make sure that // this is stored correctly in "min-max" format. - String range[] = errorDetail.split("-"); + String[] range = errorDetail.split("-"); int min = Integer.parseInt(range[0]); int max = Integer.parseInt(range[1]); int error = Integer.parseInt(iwlanErrorDetail); @@ -866,18 +992,22 @@ public class ErrorPolicyManager { } void log() { - Log.d(LOG_TAG, "ErrorType: " + mErrorType); - Log.d(LOG_TAG, "ErrorDetail: " + Arrays.toString(mErrorDetails.toArray())); - Log.d(LOG_TAG, "RetryArray: " + Arrays.toString(mRetryArray.toArray())); - Log.d(LOG_TAG, "unthrottlingEvents: " + Arrays.toString(mUnthrottlingEvents.toArray())); + Log.d(LOG_TAG, "ErrorType: " + errorType()); + Log.d(LOG_TAG, "ErrorDetail: " + Arrays.toString(errorDetails().toArray())); + Log.d(LOG_TAG, "RetryArray: " + Arrays.toString(retryArray().toArray())); + Log.d( + LOG_TAG, + "InfiniteRetriesWithLastRetryTime: " + infiniteRetriesWithLastRetryTime()); + Log.d( + LOG_TAG, + "UnthrottlingEvents: " + Arrays.toString(unthrottlingEvents().toArray())); + Log.d(LOG_TAG, "NumAttemptsPerFqdn: " + numAttemptsPerFqdn()); + Log.d(LOG_TAG, "handoverAttemptCount: " + handoverAttemptCount()); } boolean isFallback() { - if ((mErrorType == FALLBACK_ERROR_TYPE) - || (mErrorDetails.size() == 1 && mErrorDetails.get(0).equals("*"))) { - return true; - } - return false; + return (errorType() == FALLBACK_ERROR_TYPE) + || (errorDetails().size() == 1 && errorDetails().get(0).equals("*")); } String getGenericErrorDetailString(IwlanError iwlanError) { @@ -892,7 +1022,25 @@ public class ErrorPolicyManager { case IwlanError.TUNNEL_TRANSFORM_FAILED: ret = "TUNNEL_TRANSFORM_FAILED"; break; + case IwlanError.IKE_NETWORK_LOST_EXCEPTION: + ret = "IKE_NETWORK_LOST_EXCEPTION"; + break; + case IwlanError.EPDG_ADDRESS_ONLY_IPV4_ALLOWED: + ret = "EPDG_ADDRESS_ONLY_IPV4_ALLOWED"; + break; + case IwlanError.EPDG_ADDRESS_ONLY_IPV6_ALLOWED: + ret = "EPDG_ADDRESS_ONLY_IPV6_ALLOWED"; + break; // TODO: Add TIMEOUT_EXCEPTION processing + case IwlanError.IKE_INIT_TIMEOUT: + ret = "IKE_INIT_TIMEOUT"; + break; + case IwlanError.IKE_MOBILITY_TIMEOUT: + ret = "IKE_MOBILITY_TIMEOUT"; + break; + case IwlanError.IKE_DPD_TIMEOUT: + ret = "IKE_DPD_TIMEOUT"; + break; } return ret; } @@ -901,16 +1049,19 @@ public class ErrorPolicyManager { class ErrorInfo { IwlanError mError; ErrorPolicy mErrorPolicy; + + // For the lifetime of the ErrorInfo object, this is a monotonically incremented value that + // can go beyond the size of mErrorPolicy's mRetryArray. int mCurrentRetryIndex; long mLastErrorTime; - boolean mIsBackOffTimeValid = false; + boolean mIsBackOffTimeValid; long mBackOffTime; ErrorInfo(IwlanError error, ErrorPolicy errorPolicy) { mError = error; mErrorPolicy = errorPolicy; mCurrentRetryIndex = -1; - mLastErrorTime = new Date().getTime(); + mLastErrorTime = IwlanHelper.elapsedRealtime(); } ErrorInfo(IwlanError error, ErrorPolicy errorPolicy, long backOffTime) { @@ -919,7 +1070,7 @@ public class ErrorPolicyManager { mCurrentRetryIndex = -1; mIsBackOffTimeValid = true; mBackOffTime = backOffTime; - mLastErrorTime = new Date().getTime(); + mLastErrorTime = IwlanHelper.elapsedRealtime(); } /** @@ -931,7 +1082,7 @@ public class ErrorPolicyManager { return -1; } long time = mErrorPolicy.getRetryTime(++mCurrentRetryIndex); - mLastErrorTime = new Date().getTime(); + mLastErrorTime = IwlanHelper.elapsedRealtime(); Log.d(LOG_TAG, "Current RetryArray index: " + mCurrentRetryIndex + " time: " + time); return time; } @@ -950,7 +1101,7 @@ public class ErrorPolicyManager { } else { time = TimeUnit.SECONDS.toMillis(mErrorPolicy.getRetryTime(mCurrentRetryIndex)); } - long currentTime = new Date().getTime(); + long currentTime = IwlanHelper.elapsedRealtime(); time = Math.max(0, time - (currentTime - mLastErrorTime)); Log.d( LOG_TAG, @@ -958,13 +1109,18 @@ public class ErrorPolicyManager { return time; } + int getCurrentFqdnIndex(int numFqdns) { + ErrorPolicy errorPolicy = getErrorPolicy(); + return errorPolicy.getCurrentFqdnIndex(mCurrentRetryIndex, numFqdns); + } + boolean isBackOffTimeValid() { return mIsBackOffTimeValid; } void setBackOffTime(long backOffTime) { mBackOffTime = backOffTime; - mLastErrorTime = new Date().getTime(); + mLastErrorTime = IwlanHelper.elapsedRealtime(); } boolean canBringUpTunnel() { @@ -979,7 +1135,7 @@ public class ErrorPolicyManager { retryTime = TimeUnit.SECONDS.toMillis(mErrorPolicy.getRetryTime(mCurrentRetryIndex)); } - long currentTime = new Date().getTime(); + long currentTime = IwlanHelper.elapsedRealtime(); long timeDifference = currentTime - mLastErrorTime; if (timeDifference < retryTime) { ret = false; @@ -987,6 +1143,15 @@ public class ErrorPolicyManager { return ret; } + boolean shouldRetryWithInitialAttach() { + // UE should only uses initial attach to reset network failure, not for UE internal or + // DNS errors. When the number of handover failures due to network issues exceeds the + // configured threshold, UE should request network with initial attach instead of + // handover request. + return mErrorPolicy.getErrorType() == IKE_PROTOCOL_ERROR_TYPE + && mCurrentRetryIndex + 1 >= mErrorPolicy.getHandoverAttemptCount(); + } + ErrorPolicy getErrorPolicy() { return mErrorPolicy; } @@ -996,15 +1161,23 @@ public class ErrorPolicyManager { } } + static class ApnWithIwlanError { + @NonNull final String mApn; + @NonNull final IwlanError mIwlanError; + + ApnWithIwlanError(@NonNull String apn, @NonNull IwlanError iwlanError) { + mApn = apn; + mIwlanError = iwlanError; + } + } + private boolean isValidCarrierConfigChangedEvent(int currentCarrierId) { String errorPolicyConfig = - (String) IwlanHelper.getConfig(KEY_ERROR_POLICY_CONFIG_STRING, mContext, mSlotId); - boolean isValidEvent = - (currentCarrierId != carrierId) - || (carrierConfigErrorPolicyString == null) - || (errorPolicyConfig != null - && !carrierConfigErrorPolicyString.equals(errorPolicyConfig)); - return isValidEvent; + IwlanHelper.getConfig(KEY_ERROR_POLICY_CONFIG_STRING, mContext, mSlotId); + return (currentCarrierId != carrierId) + || (carrierConfigErrorPolicyString == null) + || (errorPolicyConfig != null + && !carrierConfigErrorPolicyString.equals(errorPolicyConfig)); } private final class EpmHandler extends Handler { @@ -1042,12 +1215,12 @@ public class ErrorPolicyManager { } @VisibleForTesting - class ErrorStats { + static class ErrorStats { @VisibleForTesting Map<String, Map<String, Long>> mStats = new HashMap<>(); private Date mStartTime; - private int mStatCount = 0; - private final int APN_COUNT_MAX = 10; - private final int ERROR_COUNT_MAX = 1000; + private int mStatCount; + private static final int APN_COUNT_MAX = 10; + private static final int ERROR_COUNT_MAX = 1000; ErrorStats() { mStartTime = Calendar.getInstance().getTime(); @@ -1059,7 +1232,7 @@ public class ErrorPolicyManager { reset(); } if (!mStats.containsKey(apn)) { - mStats.put(apn, new HashMap<String, Long>()); + mStats.put(apn, new HashMap<>()); } Map<String, Long> errorMap = mStats.get(apn); String errorString = error.toString(); @@ -1074,19 +1247,22 @@ public class ErrorPolicyManager { void reset() { mStartTime = Calendar.getInstance().getTime(); - mStats = new HashMap<String, Map<String, Long>>(); + mStats = new HashMap<>(); mStatCount = 0; } @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("mStartTime: " + mStartTime); + sb.append("mStartTime: ").append(mStartTime); sb.append("\nErrorStats"); for (Map.Entry<String, Map<String, Long>> entry : mStats.entrySet()) { - sb.append("\n\tApn: " + entry.getKey()); + sb.append("\n\tApn: ").append(entry.getKey()); for (Map.Entry<String, Long> errorEntry : entry.getValue().entrySet()) { - sb.append("\n\t " + errorEntry.getKey() + " : " + errorEntry.getValue()); + sb.append("\n\t ") + .append(errorEntry.getKey()) + .append(" : ") + .append(errorEntry.getValue()); } } return sb.toString(); diff --git a/src/com/google/android/iwlan/IwlanBroadcastReceiver.java b/src/com/google/android/iwlan/IwlanBroadcastReceiver.java index 10b10e4..e6195ba 100644 --- a/src/com/google/android/iwlan/IwlanBroadcastReceiver.java +++ b/src/com/google/android/iwlan/IwlanBroadcastReceiver.java @@ -29,6 +29,8 @@ import android.util.Log; import com.google.android.iwlan.epdg.EpdgSelector; +import java.util.Arrays; + public class IwlanBroadcastReceiver extends BroadcastReceiver { private static final String TAG = "IwlanBroadcastReceiver"; @@ -96,7 +98,12 @@ public class IwlanBroadcastReceiver extends BroadcastReceiver { int pcoId = intent.getIntExtra(TelephonyManager.EXTRA_PCO_ID, 0); byte[] pcoData = intent.getByteArrayExtra(TelephonyManager.EXTRA_PCO_VALUE); - Log.d(TAG, "PcoID:" + String.format("0x%04x", pcoId) + " PcoData:" + pcoData); + Log.d( + TAG, + "PcoID:" + + String.format("0x%04x", pcoId) + + " PcoData:" + + Arrays.toString(pcoData)); Context mContext = IwlanDataService.getContext(); diff --git a/src/com/google/android/iwlan/IwlanDataService.java b/src/com/google/android/iwlan/IwlanDataService.java index a254535..04b6789 100644 --- a/src/com/google/android/iwlan/IwlanDataService.java +++ b/src/com/google/android/iwlan/IwlanDataService.java @@ -26,18 +26,27 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; +import android.net.NetworkSpecifier; +import android.net.TelephonyNetworkSpecifier; +import android.net.TransportInfo; +import android.net.vcn.VcnTransportInfo; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; -import android.support.annotation.GuardedBy; import android.support.annotation.IntRange; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.telephony.AccessNetworkConstants.AccessNetworkType; import android.telephony.CarrierConfigManager; import android.telephony.CellInfo; +import android.telephony.CellInfoGsm; +import android.telephony.CellInfoLte; +import android.telephony.CellInfoNr; +import android.telephony.CellInfoWcdma; import android.telephony.DataFailCause; import android.telephony.TelephonyManager; import android.telephony.data.ApnSetting; @@ -51,10 +60,13 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.google.android.iwlan.TunnelMetricsInterface.OnClosedMetrics; +import com.google.android.iwlan.TunnelMetricsInterface.OnOpenedMetrics; 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.proto.MetricsAtom; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -69,45 +81,58 @@ import java.util.HashMap; import java.util.List; import java.util.LongSummaryStatistics; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; public class IwlanDataService extends DataService { private static final String TAG = IwlanDataService.class.getSimpleName(); + + private static final String CONTEXT_ATTRIBUTION_TAG = "IWLAN"; private static Context mContext; private IwlanNetworkMonitorCallback mNetworkMonitorCallback; - private HandlerThread mNetworkCallbackHandlerThread; private static boolean sNetworkConnected = false; private static Network sNetwork = null; - // TODO: Change this to a hashmap as there is only one provider per slot - private static List<IwlanDataServiceProvider> sIwlanDataServiceProviderList = - new ArrayList<IwlanDataServiceProvider>(); + private static LinkProperties sLinkProperties = null; + @VisibleForTesting Handler mIwlanDataServiceHandler; + private HandlerThread mIwlanDataServiceHandlerThread; + private static final Map<Integer, IwlanDataServiceProvider> sIwlanDataServiceProviders = + new ConcurrentHashMap<>(); + private static final int INVALID_SUB_ID = -1; + + // The current subscription with the active internet PDN. Need not be the default data sub. + // If internet is over WiFi, this value will be INVALID_SUB_ID. + private static int mConnectedDataSub = INVALID_SUB_ID; + + private static final int EVENT_BASE = IwlanEventListener.DATA_SERVICE_INTERNAL_EVENT_BASE; + private static final int EVENT_TUNNEL_OPENED = EVENT_BASE; + private static final int EVENT_TUNNEL_CLOSED = EVENT_BASE + 1; + private static final int EVENT_SETUP_DATA_CALL = EVENT_BASE + 2; + private static final int EVENT_DEACTIVATE_DATA_CALL = EVENT_BASE + 3; + private static final int EVENT_DATA_CALL_LIST_REQUEST = EVENT_BASE + 4; + private static final int EVENT_FORCE_CLOSE_TUNNEL = EVENT_BASE + 5; + private static final int EVENT_ADD_DATA_SERVICE_PROVIDER = EVENT_BASE + 6; + private static final int EVENT_REMOVE_DATA_SERVICE_PROVIDER = EVENT_BASE + 7; + private static final int EVENT_TUNNEL_OPENED_METRICS = EVENT_BASE + 8; + private static final int EVENT_TUNNEL_CLOSED_METRICS = EVENT_BASE + 9; @VisibleForTesting enum Transport { UNSPECIFIED_NETWORK, MOBILE, - WIFI; + WIFI } private static Transport sDefaultDataTransport = Transport.UNSPECIFIED_NETWORK; - enum LinkProtocolType { - UNKNOWN, - IPV4, - IPV6, - IPV4V6; - } - - private static LinkProtocolType sLinkProtocolType = LinkProtocolType.UNKNOWN; - // TODO: see if network monitor callback impl can be shared between dataservice and // networkservice + // This callback runs in the same thread as IwlanDataServiceHandler static class IwlanNetworkMonitorCallback extends ConnectivityManager.NetworkCallback { /** Called when the framework connects and has declared a new network ready for use. */ @Override - public void onAvailable(Network network) { + public void onAvailable(@NonNull Network network) { Log.d(TAG, "onAvailable: " + network); } @@ -119,7 +144,7 @@ public class IwlanDataService extends DataService { * is suddenly disconnected. */ @Override - public void onLosing(Network network, int maxMsToLive) { + public void onLosing(@NonNull Network network, int maxMsToLive) { Log.d(TAG, "onLosing: maxMsToLive: " + maxMsToLive + " network: " + network); } @@ -128,41 +153,53 @@ public class IwlanDataService extends DataService { * callback. */ @Override - public void onLost(Network network) { + public void onLost(@NonNull Network network) { Log.d(TAG, "onLost: " + network); + IwlanDataService.setConnectedDataSub(INVALID_SUB_ID); IwlanDataService.setNetworkConnected(false, network, Transport.UNSPECIFIED_NETWORK); } /** Called when the network corresponding to this request changes {@link LinkProperties}. */ @Override - public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) { + public void onLinkPropertiesChanged( + @NonNull Network network, @NonNull LinkProperties linkProperties) { Log.d(TAG, "onLinkPropertiesChanged: " + linkProperties); - if (isLinkProtocolTypeChanged(linkProperties)) { - for (IwlanDataServiceProvider dp : sIwlanDataServiceProviderList) { + + if (!network.equals(sNetwork)) { + Log.d(TAG, "Ignore LinkProperties changes for unused Network."); + return; + } + + if (!linkProperties.equals(sLinkProperties)) { + for (IwlanDataServiceProvider dp : sIwlanDataServiceProviders.values()) { dp.dnsPrefetchCheck(); + sLinkProperties = linkProperties; + dp.updateNetwork(network, linkProperties); } } } /** Called when access to the specified network is blocked or unblocked. */ @Override - public void onBlockedStatusChanged(Network network, boolean blocked) { + public void onBlockedStatusChanged(@NonNull Network network, boolean blocked) { // TODO: check if we need to handle this Log.d(TAG, "onBlockedStatusChanged: " + network + " BLOCKED:" + blocked); } @Override public void onCapabilitiesChanged( - Network network, NetworkCapabilities networkCapabilities) { + @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) { // onCapabilitiesChanged is guaranteed to be called immediately after onAvailable per // API Log.d(TAG, "onCapabilitiesChanged: " + network + " " + networkCapabilities); if (networkCapabilities != null) { if (networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) { Log.d(TAG, "Network " + network + " connected using transport MOBILE"); + IwlanDataService.setConnectedDataSub(getConnectedDataSub(networkCapabilities)); IwlanDataService.setNetworkConnected(true, network, Transport.MOBILE); } else if (networkCapabilities.hasTransport(TRANSPORT_WIFI)) { Log.d(TAG, "Network " + network + " connected using transport WIFI"); + IwlanDataService.setConnectedDataSub(INVALID_SUB_ID); IwlanDataService.setNetworkConnected(true, network, Transport.WIFI); } else { Log.w(TAG, "Network does not have cellular or wifi capability"); @@ -180,20 +217,20 @@ public class IwlanDataService extends DataService { private final String SUB_TAG; private final IwlanDataService mIwlanDataService; private final IwlanTunnelCallback mIwlanTunnelCallback; - private HandlerThread mHandlerThread; - @VisibleForTesting Handler mHandler; + private final IwlanTunnelMetricsImpl mIwlanTunnelMetrics; private boolean mWfcEnabled = false; private boolean mCarrierConfigReady = false; - private EpdgSelector mEpdgSelector; - private IwlanDataTunnelStats mTunnelStats; + private final EpdgSelector mEpdgSelector; + private final IwlanDataTunnelStats mTunnelStats; private CellInfo mCellInfo = null; + private int mCallState = TelephonyManager.CALL_STATE_IDLE; + private long mProcessingStartTime = 0; // apn to TunnelState - // Lock this at public entry and exit points if: - // 1) the function changes mTunnelStateForApn - // 2) Makes decisions based on contents of mTunnelStateForApn - @GuardedBy("mTunnelStateForApn") - private Map<String, TunnelState> mTunnelStateForApn = new ConcurrentHashMap<>(); + // Access should be serialized inside IwlanDataServiceHandler + private final Map<String, TunnelState> mTunnelStateForApn = new ConcurrentHashMap<>(); + private final Map<String, MetricsAtom> mMetricsAtomForApn = new ConcurrentHashMap<>(); + private Calendar mCalendar; // Holds the state of a tunnel (for an APN) @VisibleForTesting @@ -202,8 +239,8 @@ public class IwlanDataService extends DataService { // this should be ideally be based on path MTU discovery. 1280 is the minimum packet // size ipv6 routers have to handle so setting it to 1280 is the safest approach. // ideally it should be 1280 - tunnelling overhead ? - private static final int LINK_MTU = - 1280; // TODO: need to substract tunnelling overhead? + private static final int LINK_MTU = 1280; // TODO: need to subtract tunnelling overhead? + private static final int LINK_MTU_CST = 1200; // Reserve 80 bytes for VCN. static final int TUNNEL_DOWN = 1; static final int TUNNEL_IN_BRINGUP = 2; static final int TUNNEL_UP = 3; @@ -216,6 +253,7 @@ public class IwlanDataService extends DataService { private boolean mIsHandover; private Date mBringUpStateTime = null; private Date mUpStateTime = null; + private boolean mIsImsOrEmergency; public int getPduSessionId() { return mPduSessionId; @@ -230,7 +268,11 @@ public class IwlanDataService extends DataService { } public int getLinkMtu() { - return LINK_MTU; // TODO: need to substract tunnelling overhead + if ((sDefaultDataTransport == Transport.MOBILE) && sNetworkConnected) { + return LINK_MTU_CST; + } else { + return LINK_MTU; // TODO: need to subtract tunnelling overhead + } } public void setProtocolType(int protocolType) { @@ -264,14 +306,16 @@ public class IwlanDataService extends DataService { return mState; } - /** @param state (TunnelState.TUNNEL_DOWN|TUNNEL_UP|TUNNEL_DOWN) */ + /** + * @param state (TunnelState.TUNNEL_DOWN|TUNNEL_UP|TUNNEL_DOWN) + */ public void setState(int state) { mState = state; if (mState == TunnelState.TUNNEL_IN_BRINGUP) { - mBringUpStateTime = Calendar.getInstance().getTime(); + mBringUpStateTime = mCalendar.getTime(); } if (mState == TunnelState.TUNNEL_UP) { - mUpStateTime = Calendar.getInstance().getTime(); + mUpStateTime = mCalendar.getTime(); } } @@ -291,6 +335,18 @@ public class IwlanDataService extends DataService { return mUpStateTime; } + public Date getCurrentTime() { + return mCalendar.getTime(); + } + + public boolean getIsImsOrEmergency() { + return mIsImsOrEmergency; + } + + public void setIsImsOrEmergency(boolean isImsOrEmergency) { + mIsImsOrEmergency = isImsOrEmergency; + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); @@ -312,19 +368,23 @@ public class IwlanDataService extends DataService { tunnelState = "IN FORCE CLEAN WAS IN BRINGUP"; break; } - sb.append("\tCurrent State of this tunnel: " + mState + " " + tunnelState); - sb.append("\n\tTunnel state is in Handover: " + mIsHandover); + sb.append("\tCurrent State of this tunnel: ") + .append(mState) + .append(" ") + .append(tunnelState); + sb.append("\n\tTunnel state is in Handover: ").append(mIsHandover); if (mBringUpStateTime != null) { - sb.append("\n\tTunnel bring up initiated at: " + mBringUpStateTime); + sb.append("\n\tTunnel bring up initiated at: ").append(mBringUpStateTime); } else { sb.append("\n\tPotential leak. Null mBringUpStateTime"); } if (mUpStateTime != null) { - sb.append("\n\tTunnel is up at: " + mUpStateTime); + sb.append("\n\tTunnel is up at: ").append(mUpStateTime); } if (mUpStateTime != null && mBringUpStateTime != null) { long tunnelUpTime = mUpStateTime.getTime() - mBringUpStateTime.getTime(); - sb.append("\n\tTime taken for the tunnel to come up in ms: " + tunnelUpTime); + sb.append("\n\tTime taken for the tunnel to come up in ms: ") + .append(tunnelUpTime); } return sb.toString(); } @@ -333,10 +393,10 @@ public class IwlanDataService extends DataService { @VisibleForTesting class IwlanTunnelCallback implements EpdgTunnelManager.TunnelCallback { - DataServiceProvider mDataServiceProvider; + IwlanDataServiceProvider mIwlanDataServiceProvider; - public IwlanTunnelCallback(DataServiceProvider dsp) { - mDataServiceProvider = dsp; + public IwlanTunnelCallback(IwlanDataServiceProvider dsp) { + mIwlanDataServiceProvider = dsp; } // TODO: full implementation @@ -345,154 +405,30 @@ public class IwlanDataService extends DataService { Log.d( SUB_TAG, "Tunnel opened!. APN: " + apnName + "linkproperties: " + linkProperties); - synchronized (mTunnelStateForApn) { - TunnelState tunnelState = mTunnelStateForApn.get(apnName); - // tunnelstate should not be null, design violation. - // if its null, we should crash and debug. - tunnelState.setTunnelLinkProperties(linkProperties); - tunnelState.setState(TunnelState.TUNNEL_UP); - mTunnelStats.reportTunnelSetupSuccess(apnName, tunnelState); - - deliverCallback( - CALLBACK_TYPE_SETUP_DATACALL_COMPLETE, - DataServiceCallback.RESULT_SUCCESS, - tunnelState.getDataServiceCallback(), - apnTunnelStateToDataCallResponse(apnName)); - } + getIwlanDataServiceHandler() + .sendMessage( + getIwlanDataServiceHandler() + .obtainMessage( + EVENT_TUNNEL_OPENED, + new TunnelOpenedData( + apnName, + linkProperties, + mIwlanDataServiceProvider))); } public void onClosed(String apnName, IwlanError error) { Log.d(SUB_TAG, "Tunnel closed!. APN: " + apnName + " Error: " + error); // this is called, when a tunnel that is up, is closed. // the expectation is error==NO_ERROR for user initiated/normal close. - synchronized (mTunnelStateForApn) { - TunnelState tunnelState = mTunnelStateForApn.get(apnName); - mTunnelStats.reportTunnelDown(apnName, tunnelState); - mTunnelStateForApn.remove(apnName); - - if (tunnelState.getState() == TunnelState.TUNNEL_IN_BRINGUP - || tunnelState.getState() - == TunnelState.TUNNEL_IN_FORCE_CLEAN_WAS_IN_BRINGUP) { - DataCallResponse.Builder respBuilder = new DataCallResponse.Builder(); - respBuilder - .setId(apnName.hashCode()) - .setProtocolType(tunnelState.getProtocolType()); - - if (tunnelState.getIsHandover()) { - respBuilder.setHandoverFailureMode( - DataCallResponse - .HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER); - } else { - respBuilder.setHandoverFailureMode( - DataCallResponse - .HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL); - } - - if (tunnelState.getState() == TunnelState.TUNNEL_IN_BRINGUP) { - respBuilder.setCause( - ErrorPolicyManager.getInstance(mContext, getSlotIndex()) - .getDataFailCause(apnName)); - respBuilder.setRetryDurationMillis( - ErrorPolicyManager.getInstance(mContext, getSlotIndex()) - .getCurrentRetryTimeMs(apnName)); - } else if (tunnelState.getState() - == TunnelState.TUNNEL_IN_FORCE_CLEAN_WAS_IN_BRINGUP) { - respBuilder.setCause(DataFailCause.IWLAN_NETWORK_FAILURE); - respBuilder.setRetryDurationMillis(5000); - } - - deliverCallback( - CALLBACK_TYPE_SETUP_DATACALL_COMPLETE, - DataServiceCallback.RESULT_SUCCESS, - tunnelState.getDataServiceCallback(), - respBuilder.build()); - return; - } - - // iwlan service triggered teardown - if (tunnelState.getState() == TunnelState.TUNNEL_IN_BRINGDOWN) { - - // IO exception happens when IKE library fails to retransmit requests. - // This can happen for multiple reasons: - // 1. Network disconnection due to wifi off. - // 2. Epdg server does not respond. - // 3. Socket send/receive fails. - // Ignore this during tunnel bring down. - if (error.getErrorType() != IwlanError.NO_ERROR - && error.getErrorType() != IwlanError.IKE_INTERNAL_IO_EXCEPTION) { - Log.e(SUB_TAG, "Unexpected error during tunnel bring down: " + error); - } - - deliverCallback( - CALLBACK_TYPE_DEACTIVATE_DATACALL_COMPLETE, - DataServiceCallback.RESULT_SUCCESS, - tunnelState.getDataServiceCallback(), - null); - - return; - } - - // just update list of data calls. No way to send error up - notifyDataCallListChanged(getCallList()); - } - } - } - - private final class DSPHandler extends Handler { - private final String TAG = - IwlanDataService.class.getSimpleName() - + DSPHandler.class.getSimpleName() - + getSlotIndex(); - - @Override - public void handleMessage(Message msg) { - Log.d(TAG, "msg.what = " + msg.what); - switch (msg.what) { - case IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT: - Log.d(TAG, "On CARRIER_CONFIG_CHANGED_EVENT"); - mCarrierConfigReady = true; - dnsPrefetchCheck(); - break; - case IwlanEventListener.CARRIER_CONFIG_UNKNOWN_CARRIER_EVENT: - Log.d(TAG, "On CARRIER_CONFIG_UNKNOWN_CARRIER_EVENT"); - mCarrierConfigReady = false; - break; - case IwlanEventListener.WIFI_CALLING_ENABLE_EVENT: - Log.d(TAG, "On WIFI_CALLING_ENABLE_EVENT"); - mWfcEnabled = true; - dnsPrefetchCheck(); - break; - case IwlanEventListener.WIFI_CALLING_DISABLE_EVENT: - Log.d(TAG, "On WIFI_CALLING_DISABLE_EVENT"); - mWfcEnabled = false; - break; - case IwlanEventListener.CELLINFO_CHANGED_EVENT: - Log.d(TAG, "On CELLINFO_CHANGED_EVENT"); - List<CellInfo> cellInfolist = (List<CellInfo>) msg.obj; - - if (cellInfolist != null && isRegisteredCellInfoChanged(cellInfolist)) { - int[] addrResolutionMethods = - IwlanHelper.getConfig( - CarrierConfigManager.Iwlan - .KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY, - mContext, - getSlotIndex()); - for (int addrResolutionMethod : addrResolutionMethods) { - if (addrResolutionMethod - == CarrierConfigManager.Iwlan.EPDG_ADDRESS_CELLULAR_LOC) { - dnsPrefetchCheck(); - } - } - } - break; - default: - Log.d(TAG, "Unknown message received!"); - break; - } - } - - DSPHandler(Looper looper) { - super(looper); + getIwlanDataServiceHandler() + .sendMessage( + getIwlanDataServiceHandler() + .obtainMessage( + EVENT_TUNNEL_CLOSED, + new TunnelClosedData( + apnName, + error, + mIwlanDataServiceProvider))); } } @@ -522,10 +458,9 @@ public class IwlanDataService extends DataService { private long statCount; private final long COUNT_MAX = 1000; - private final int APN_COUNT_MAX = 10; public IwlanDataTunnelStats() { - mStartTime = Calendar.getInstance().getTime(); + mStartTime = mCalendar.getTime(); statCount = 0L; } @@ -566,7 +501,7 @@ public class IwlanDataService extends DataService { } // Unsolicited tunnel down as tunnel has to be in BRINGDOWN if - // there is a deactivate call associated with this. + // there is a deactivateDataCall() associated with this. if (tunnelState.getState() == TunnelState.TUNNEL_UP) { if (!mUnsolTunnelDownCounts.containsKey(apn)) { mUnsolTunnelDownCounts.put(apn, 0L); @@ -574,7 +509,7 @@ public class IwlanDataService extends DataService { long count = mUnsolTunnelDownCounts.get(apn); mUnsolTunnelDownCounts.put(apn, ++count); } - Date currentTime = Calendar.getInstance().getTime(); + Date currentTime = tunnelState.getCurrentTime(); Date upTime = tunnelState.getUpStateTime(); if (upTime != null) { if (!mTunnelUpStats.containsKey(apn)) { @@ -587,48 +522,46 @@ public class IwlanDataService extends DataService { } boolean maxApnReached() { - if (mTunnelSetupSuccessStats.size() >= APN_COUNT_MAX + int APN_COUNT_MAX = 10; + return mTunnelSetupSuccessStats.size() >= APN_COUNT_MAX || mTunnelSetupFailureCounts.size() >= APN_COUNT_MAX || mUnsolTunnelDownCounts.size() >= APN_COUNT_MAX - || mTunnelUpStats.size() >= APN_COUNT_MAX) { - return true; - } - return false; + || mTunnelUpStats.size() >= APN_COUNT_MAX; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("IwlanDataTunnelStats:"); - sb.append("\n\tmStartTime: " + mStartTime); + sb.append("\n\tmStartTime: ").append(mStartTime); sb.append("\n\ttunnelSetupSuccessStats:"); for (Map.Entry<String, LongSummaryStatistics> entry : mTunnelSetupSuccessStats.entrySet()) { - sb.append("\n\t Apn: " + entry.getKey()); - sb.append("\n\t " + entry.getValue()); + sb.append("\n\t Apn: ").append(entry.getKey()); + sb.append("\n\t ").append(entry.getValue()); } sb.append("\n\ttunnelUpStats:"); for (Map.Entry<String, LongSummaryStatistics> entry : mTunnelUpStats.entrySet()) { - sb.append("\n\t Apn: " + entry.getKey()); - sb.append("\n\t " + entry.getValue()); + sb.append("\n\t Apn: ").append(entry.getKey()); + sb.append("\n\t ").append(entry.getValue()); } sb.append("\n\ttunnelSetupFailureCounts: "); for (Map.Entry<String, Long> entry : mTunnelSetupFailureCounts.entrySet()) { - sb.append("\n\t Apn: " + entry.getKey()); - sb.append("\n\t counts: " + entry.getValue()); + sb.append("\n\t Apn: ").append(entry.getKey()); + sb.append("\n\t counts: ").append(entry.getValue()); } sb.append("\n\tunsolTunnelDownCounts: "); for (Map.Entry<String, Long> entry : mTunnelSetupFailureCounts.entrySet()) { - sb.append("\n\t Apn: " + entry.getKey()); - sb.append("\n\t counts: " + entry.getValue()); + sb.append("\n\t Apn: ").append(entry.getKey()); + sb.append("\n\t counts: ").append(entry.getValue()); } - sb.append("\n\tendTime: " + Calendar.getInstance().getTime()); + sb.append("\n\tendTime: ").append(mCalendar.getTime()); return sb.toString(); } private void reset() { - mStartTime = Calendar.getInstance().getTime(); + mStartTime = mCalendar.getTime(); mTunnelSetupSuccessStats = new HashMap<String, LongSummaryStatistics>(); mTunnelUpStats = new HashMap<String, LongSummaryStatistics>(); mTunnelSetupFailureCounts = new HashMap<String, Long>(); @@ -637,12 +570,6 @@ public class IwlanDataService extends DataService { } } - Looper getLooper() { - mHandlerThread = new HandlerThread("DSPHandlerThread"); - mHandlerThread.start(); - return mHandlerThread.getLooper(); - } - /** * Constructor * @@ -657,26 +584,25 @@ public class IwlanDataService extends DataService { // get reference to resolver mIwlanDataService = iwlanDataService; mIwlanTunnelCallback = new IwlanTunnelCallback(this); + mIwlanTunnelMetrics = new IwlanTunnelMetricsImpl(this, getIwlanDataServiceHandler()); mEpdgSelector = EpdgSelector.getSelectorInstance(mContext, slotIndex); + mCalendar = Calendar.getInstance(); mTunnelStats = new IwlanDataTunnelStats(); // Register IwlanEventListener - initHandler(); List<Integer> events = new ArrayList<Integer>(); events.add(IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT); events.add(IwlanEventListener.CARRIER_CONFIG_UNKNOWN_CARRIER_EVENT); events.add(IwlanEventListener.WIFI_CALLING_ENABLE_EVENT); events.add(IwlanEventListener.WIFI_CALLING_DISABLE_EVENT); + events.add(IwlanEventListener.CROSS_SIM_CALLING_ENABLE_EVENT); events.add(IwlanEventListener.CELLINFO_CHANGED_EVENT); - IwlanEventListener.getInstance(mContext, slotIndex).addEventListener(events, mHandler); - } - - void initHandler() { - mHandler = new DSPHandler(getLooper()); + events.add(IwlanEventListener.CALL_STATE_CHANGED_EVENT); + IwlanEventListener.getInstance(mContext, slotIndex) + .addEventListener(events, getIwlanDataServiceHandler()); } - @VisibleForTesting - EpdgTunnelManager getTunnelManager() { + private EpdgTunnelManager getTunnelManager() { return EpdgTunnelManager.getInstance(mContext, getSlotIndex()); } @@ -693,23 +619,30 @@ public class IwlanDataService extends DataService { .setProtocolType(tunnelState.getProtocolType()) .setCause(DataFailCause.NONE); - if (tunnelState.getState() != TunnelState.TUNNEL_UP) { - // no need to fill additional params - return responseBuilder.setLinkStatus(DataCallResponse.LINK_STATUS_UNKNOWN).build(); + responseBuilder.setLinkStatus(DataCallResponse.LINK_STATUS_INACTIVE); + int state = tunnelState.getState(); + + if (state == TunnelState.TUNNEL_UP) { + responseBuilder.setLinkStatus(DataCallResponse.LINK_STATUS_ACTIVE); + } + + TunnelLinkProperties tunnelLinkProperties = tunnelState.getTunnelLinkProperties(); + if (tunnelLinkProperties == null) { + Log.d(TAG, "PDN with empty linkproperties. TunnelState : " + state); + return responseBuilder.build(); } // fill wildcard address for gatewayList (used by DataConnection to add routes) List<InetAddress> gatewayList = new ArrayList<>(); - List<LinkAddress> linkAddrList = - tunnelState.getTunnelLinkProperties().internalAddresses(); - if (linkAddrList.stream().anyMatch(t -> t.isIpv4())) { + List<LinkAddress> linkAddrList = tunnelLinkProperties.internalAddresses(); + if (linkAddrList.stream().anyMatch(LinkAddress::isIpv4)) { try { gatewayList.add(Inet4Address.getByName("0.0.0.0")); } catch (UnknownHostException e) { // should never happen for static string 0.0.0.0 } } - if (linkAddrList.stream().anyMatch(t -> t.isIpv6())) { + if (linkAddrList.stream().anyMatch(LinkAddress::isIpv6)) { try { gatewayList.add(Inet6Address.getByName("::")); } catch (UnknownHostException e) { @@ -717,18 +650,16 @@ public class IwlanDataService extends DataService { } } - if (tunnelState.getTunnelLinkProperties().sliceInfo().isPresent()) { - responseBuilder.setSliceInfo( - tunnelState.getTunnelLinkProperties().sliceInfo().get()); + if (tunnelLinkProperties.sliceInfo().isPresent()) { + responseBuilder.setSliceInfo(tunnelLinkProperties.sliceInfo().get()); } return responseBuilder .setAddresses(linkAddrList) - .setDnsAddresses(tunnelState.getTunnelLinkProperties().dnsAddresses()) - .setPcscfAddresses(tunnelState.getTunnelLinkProperties().pcscfAddresses()) - .setInterfaceName(tunnelState.getTunnelLinkProperties().ifaceName()) + .setDnsAddresses(tunnelLinkProperties.dnsAddresses()) + .setPcscfAddresses(tunnelLinkProperties.pcscfAddresses()) + .setInterfaceName(tunnelLinkProperties.ifaceName()) .setGatewayAddresses(gatewayList) - .setLinkStatus(DataCallResponse.LINK_STATUS_ACTIVE) .setMtu(tunnelState.getLinkMtu()) .setMtuV4(tunnelState.getLinkMtu()) .setMtuV6(tunnelState.getLinkMtu()) @@ -827,6 +758,7 @@ public class IwlanDataService extends DataService { boolean matchAllRuleAllowed, @NonNull DataServiceCallback callback) { + mProcessingStartTime = System.currentTimeMillis(); Log.d( SUB_TAG, "Setup data call with network: " @@ -844,128 +776,50 @@ public class IwlanDataService extends DataService { + ", pduSessionId: " + pduSessionId); - // Framework will never call bringup on the same APN back 2 back. - // but add a safety check - if ((accessNetworkType != AccessNetworkType.IWLAN) - || (dataProfile == null) - || (linkProperties == null && reason == DataService.REQUEST_REASON_HANDOVER)) { - - deliverCallback( - CALLBACK_TYPE_SETUP_DATACALL_COMPLETE, - DataServiceCallback.RESULT_ERROR_INVALID_ARG, - callback, - null); - return; - } - - synchronized (mTunnelStateForApn) { - boolean isDDS = IwlanHelper.isDefaultDataSlot(mContext, getSlotIndex()); - boolean isCSTEnabled = - IwlanHelper.isCrossSimCallingEnabled(mContext, getSlotIndex()); - boolean networkConnected = isNetworkConnected(isDDS, isCSTEnabled); - Log.d( - SUB_TAG, - "isDds: " - + isDDS - + ", isCstEnabled: " - + isCSTEnabled - + ", transport: " - + sDefaultDataTransport); - - if (networkConnected == false) { - deliverCallback( - CALLBACK_TYPE_SETUP_DATACALL_COMPLETE, - 5 /* DataServiceCallback.RESULT_ERROR_TEMPORARILY_UNAVAILABLE */, + SetupDataCallData setupDataCallData = + new SetupDataCallData( + accessNetworkType, + dataProfile, + isRoaming, + allowRoaming, + reason, + linkProperties, + pduSessionId, + sliceInfo, + trafficDescriptor, + matchAllRuleAllowed, callback, - null); - return; - } - - TunnelState tunnelState = mTunnelStateForApn.get(dataProfile.getApn()); - - // Return the existing PDN if the pduSessionId is the same and the tunnel state is - // TUNNEL_UP. - if (tunnelState != null) { - if (tunnelState.getPduSessionId() == pduSessionId - && tunnelState.getState() == TunnelState.TUNNEL_UP) { - Log.w( - SUB_TAG, - "The tunnel for " + dataProfile.getApn() + " already exists."); - deliverCallback( - CALLBACK_TYPE_SETUP_DATACALL_COMPLETE, - DataServiceCallback.RESULT_SUCCESS, - callback, - apnTunnelStateToDataCallResponse(dataProfile.getApn())); - return; - } else { - Log.e( - SUB_TAG, - "Force close the existing PDN. pduSessionId = " - + tunnelState.getPduSessionId() - + " Tunnel State = " - + tunnelState.getState()); - getTunnelManager().closeTunnel(dataProfile.getApn(), true /* forceClose */); - deliverCallback( - CALLBACK_TYPE_SETUP_DATACALL_COMPLETE, - 5 /* DataServiceCallback.RESULT_ERROR_TEMPORARILY_UNAVAILABLE */, - callback, - null); - return; - } - } + this); - TunnelSetupRequest.Builder tunnelReqBuilder = - TunnelSetupRequest.builder() - .setApnName(dataProfile.getApn()) - .setNetwork(sNetwork) - .setIsRoaming(isRoaming) - .setPduSessionId(pduSessionId) - .setApnIpProtocol( - isRoaming - ? dataProfile.getRoamingProtocolType() - : dataProfile.getProtocolType()); - - if (reason == DataService.REQUEST_REASON_HANDOVER) { - // for now assume that, at max, only one address of eachtype (v4/v6). - // TODO: Check if multiple ips can be sent in ike tunnel setup - for (LinkAddress lAddr : linkProperties.getLinkAddresses()) { - if (lAddr.isIpv4()) { - tunnelReqBuilder.setSrcIpv4Address(lAddr.getAddress()); - } else if (lAddr.isIpv6()) { - tunnelReqBuilder.setSrcIpv6Address(lAddr.getAddress()); - tunnelReqBuilder.setSrcIpv6AddressPrefixLength(lAddr.getPrefixLength()); - } - } - } + int networkTransport = -1; + if (sDefaultDataTransport == Transport.MOBILE) { + networkTransport = TRANSPORT_CELLULAR; + } else if (sDefaultDataTransport == Transport.WIFI) { + networkTransport = TRANSPORT_WIFI; + } - int apnTypeBitmask = dataProfile.getSupportedApnTypesBitmask(); - boolean isIMS = (apnTypeBitmask & ApnSetting.TYPE_IMS) == ApnSetting.TYPE_IMS; - boolean isEmergency = - (apnTypeBitmask & ApnSetting.TYPE_EMERGENCY) == ApnSetting.TYPE_EMERGENCY; - tunnelReqBuilder.setRequestPcscf(isIMS || isEmergency); - tunnelReqBuilder.setIsEmergency(isEmergency); - - setTunnelState( - dataProfile, - callback, - TunnelState.TUNNEL_IN_BRINGUP, - null, + if (dataProfile != null) { + this.setMetricsAtom( + // ApnName + dataProfile.getApnSetting().getApnName(), + // ApnType + dataProfile.getApnSetting().getApnTypeBitmask(), + // IsHandover (reason == DataService.REQUEST_REASON_HANDOVER), - pduSessionId); - - boolean result = - getTunnelManager() - .bringUpTunnel(tunnelReqBuilder.build(), getIwlanTunnelCallback()); - Log.d(SUB_TAG, "bringup Tunnel with result:" + result); - if (!result) { - deliverCallback( - CALLBACK_TYPE_SETUP_DATACALL_COMPLETE, - DataServiceCallback.RESULT_ERROR_INVALID_ARG, - callback, - null); - return; - } + // Source Rat + getCurrentCellularRat(), + // IsRoaming + isRoaming, + // Is Network Connected + sNetworkConnected, + // Transport Type + networkTransport); } + + getIwlanDataServiceHandler() + .sendMessage( + getIwlanDataServiceHandler() + .obtainMessage(EVENT_SETUP_DATA_CALL, setupDataCallData)); } /** @@ -992,49 +846,38 @@ public class IwlanDataService extends DataService { + "callback: " + callback); - synchronized (mTunnelStateForApn) { - for (String apnName : mTunnelStateForApn.keySet()) { - if (apnName.hashCode() == cid) { - /* - No need to check state since dataconnection in framework serializes - setup and deactivate calls using callId/cid. - */ - mTunnelStateForApn.get(apnName).setState(TunnelState.TUNNEL_IN_BRINGDOWN); - mTunnelStateForApn.get(apnName).setDataServiceCallback(callback); - boolean isConnected = - isNetworkConnected( - IwlanHelper.isDefaultDataSlot(mContext, getSlotIndex()), - IwlanHelper.isCrossSimCallingEnabled( - mContext, getSlotIndex())); - getTunnelManager().closeTunnel(apnName, !isConnected); - return; - } - } + DeactivateDataCallData deactivateDataCallData = + new DeactivateDataCallData(cid, reason, callback, this); - deliverCallback( - CALLBACK_TYPE_DEACTIVATE_DATACALL_COMPLETE, - DataServiceCallback.RESULT_ERROR_INVALID_ARG, - callback, - null); - } + getIwlanDataServiceHandler() + .sendMessage( + getIwlanDataServiceHandler() + .obtainMessage( + EVENT_DEACTIVATE_DATA_CALL, deactivateDataCallData)); } public void forceCloseTunnelsInDeactivatingState() { - synchronized (mTunnelStateForApn) { - for (Map.Entry<String, TunnelState> entry : mTunnelStateForApn.entrySet()) { - TunnelState tunnelState = entry.getValue(); - if (tunnelState.getState() == TunnelState.TUNNEL_IN_BRINGDOWN) { - getTunnelManager().closeTunnel(entry.getKey(), true); - } + for (Map.Entry<String, TunnelState> entry : mTunnelStateForApn.entrySet()) { + TunnelState tunnelState = entry.getValue(); + if (tunnelState.getState() == TunnelState.TUNNEL_IN_BRINGDOWN) { + getTunnelManager() + .closeTunnel( + entry.getKey(), + true /* forceClose */, + getIwlanTunnelCallback(), + getIwlanTunnelMetrics()); } } } void forceCloseTunnels() { - synchronized (mTunnelStateForApn) { - for (Map.Entry<String, TunnelState> entry : mTunnelStateForApn.entrySet()) { - getTunnelManager().closeTunnel(entry.getKey(), true); - } + for (Map.Entry<String, TunnelState> entry : mTunnelStateForApn.entrySet()) { + getTunnelManager() + .closeTunnel( + entry.getKey(), + true /* forceClose */, + getIwlanTunnelCallback(), + getIwlanTunnelMetrics()); } } @@ -1045,11 +888,13 @@ public class IwlanDataService extends DataService { */ @Override public void requestDataCallList(DataServiceCallback callback) { - deliverCallback( - CALLBACK_TYPE_GET_DATACALL_LIST_COMPLETE, - DataServiceCallback.RESULT_SUCCESS, - callback, - null); + getIwlanDataServiceHandler() + .sendMessage( + getIwlanDataServiceHandler() + .obtainMessage( + EVENT_DATA_CALL_LIST_REQUEST, + new DataCallRequestData( + callback, IwlanDataServiceProvider.this))); } @VisibleForTesting @@ -1059,14 +904,41 @@ public class IwlanDataService extends DataService { int tunnelStatus, TunnelLinkProperties linkProperties, boolean isHandover, - int pduSessionId) { + int pduSessionId, + boolean isImsOrEmergency) { TunnelState tunnelState = new TunnelState(callback); tunnelState.setState(tunnelStatus); - tunnelState.setProtocolType(dataProfile.getProtocolType()); + tunnelState.setProtocolType(dataProfile.getApnSetting().getProtocol()); tunnelState.setTunnelLinkProperties(linkProperties); tunnelState.setIsHandover(isHandover); tunnelState.setPduSessionId(pduSessionId); - mTunnelStateForApn.put(dataProfile.getApn(), tunnelState); + tunnelState.setIsImsOrEmergency(isImsOrEmergency); + mTunnelStateForApn.put(dataProfile.getApnSetting().getApnName(), tunnelState); + } + + @VisibleForTesting + void setMetricsAtom( + String apnName, + int apntype, + boolean isHandover, + int sourceRat, + boolean isRoaming, + boolean isNetworkConnected, + int transportType) { + MetricsAtom metricsAtom = new MetricsAtom(); + metricsAtom.setApnType(apntype); + metricsAtom.setIsHandover(isHandover); + metricsAtom.setSourceRat(sourceRat); + metricsAtom.setIsCellularRoaming(isRoaming); + metricsAtom.setIsNetworkConnected(isNetworkConnected); + metricsAtom.setTransportType(transportType); + mMetricsAtomForApn.put(apnName, metricsAtom); + } + + @VisibleForTesting + @Nullable + public MetricsAtom getMetricsAtomByApn(String apnName) { + return mMetricsAtomForApn.get(apnName); } @VisibleForTesting @@ -1075,31 +947,40 @@ public class IwlanDataService extends DataService { } @VisibleForTesting + public IwlanTunnelMetricsImpl getIwlanTunnelMetrics() { + return mIwlanTunnelMetrics; + } + + @VisibleForTesting IwlanDataTunnelStats getTunnelStats() { return mTunnelStats; } - private void updateNetwork(Network network) { - if (network != null) { - synchronized (mTunnelStateForApn) { - for (Map.Entry<String, TunnelState> entry : mTunnelStateForApn.entrySet()) { - TunnelState tunnelState = entry.getValue(); - if (tunnelState.getState() == TunnelState.TUNNEL_IN_BRINGUP) { - // force close tunnels in bringup since IKE lib only supports - // updating network for tunnels that are already up. - // This may not result in actual closing of Ike Session since - // epdg selection may not be complete yet. - tunnelState.setState(TunnelState.TUNNEL_IN_FORCE_CLEAN_WAS_IN_BRINGUP); - getTunnelManager().closeTunnel(entry.getKey(), true); - } else { - if (mIwlanDataService.isNetworkConnected( - IwlanHelper.isDefaultDataSlot(mContext, getSlotIndex()), - IwlanHelper.isCrossSimCallingEnabled( - mContext, getSlotIndex()))) { - getTunnelManager().updateNetwork(network, entry.getKey()); - } - } - } + private void updateNetwork( + @Nullable Network network, @Nullable LinkProperties linkProperties) { + if (mIwlanDataService.isNetworkConnected( + isActiveDataOnOtherSub(getSlotIndex()), + IwlanHelper.isCrossSimCallingEnabled(mContext, getSlotIndex()))) { + getTunnelManager().updateNetwork(network, linkProperties); + } + + if (Objects.equals(network, sNetwork)) { + return; + } + for (Map.Entry<String, TunnelState> entry : mTunnelStateForApn.entrySet()) { + TunnelState tunnelState = entry.getValue(); + if (tunnelState.getState() == TunnelState.TUNNEL_IN_BRINGUP) { + // force close tunnels in bringup since IKE lib only supports + // updating network for tunnels that are already up. + // This may not result in actual closing of Ike Session since + // epdg selection may not be complete yet. + tunnelState.setState(TunnelState.TUNNEL_IN_FORCE_CLEAN_WAS_IN_BRINGUP); + getTunnelManager() + .closeTunnel( + entry.getKey(), + true /* forceClose */, + getIwlanTunnelCallback(), + getIwlanTunnelMetrics()); } } } @@ -1122,34 +1003,91 @@ public class IwlanDataService extends DataService { private void dnsPrefetchCheck() { boolean networkConnected = mIwlanDataService.isNetworkConnected( - IwlanHelper.isDefaultDataSlot(mContext, getSlotIndex()), + isActiveDataOnOtherSub(getSlotIndex()), IwlanHelper.isCrossSimCallingEnabled(mContext, getSlotIndex())); /* Check if we need to do prefecting */ - synchronized (mTunnelStateForApn) { - if (networkConnected == true - && mCarrierConfigReady == true - && mWfcEnabled == true - && mTunnelStateForApn.isEmpty()) { - - // Get roaming status - TelephonyManager telephonyManager = - mContext.getSystemService(TelephonyManager.class); - telephonyManager = - telephonyManager.createForSubscriptionId( - IwlanHelper.getSubId(mContext, getSlotIndex())); - boolean isRoaming = telephonyManager.isNetworkRoaming(); - Log.d(TAG, "Trigger EPDG prefetch. Roaming=" + isRoaming); - - prefetchEpdgServerList(mIwlanDataService.sNetwork, isRoaming); - } + if (networkConnected + && mCarrierConfigReady + && mWfcEnabled + && mTunnelStateForApn.isEmpty()) { + + // Get roaming status + TelephonyManager telephonyManager = + mContext.getSystemService(TelephonyManager.class); + telephonyManager = + telephonyManager.createForSubscriptionId( + IwlanHelper.getSubId(mContext, getSlotIndex())); + boolean isRoaming = telephonyManager.isNetworkRoaming(); + Log.d(TAG, "Trigger EPDG prefetch. Roaming=" + isRoaming); + + prefetchEpdgServerList(mIwlanDataService.sNetwork, isRoaming); } } private void prefetchEpdgServerList(Network network, boolean isRoaming) { mEpdgSelector.getValidatedServerList( - 0, EpdgSelector.PROTO_FILTER_IPV4V6, isRoaming, false, network, null); + 0, + EpdgSelector.PROTO_FILTER_IPV4V6, + EpdgSelector.SYSTEM_PREFERRED, + isRoaming, + false, + network, + null); mEpdgSelector.getValidatedServerList( - 0, EpdgSelector.PROTO_FILTER_IPV4V6, isRoaming, true, network, null); + 0, + EpdgSelector.PROTO_FILTER_IPV4V6, + EpdgSelector.SYSTEM_PREFERRED, + isRoaming, + true, + network, + null); + } + + private int getCurrentCellularRat() { + TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class); + telephonyManager = + telephonyManager.createForSubscriptionId( + IwlanHelper.getSubId(mContext, getSlotIndex())); + List<CellInfo> cellInfoList = telephonyManager.getAllCellInfo(); + if (cellInfoList == null) { + Log.e(TAG, "cellInfoList is NULL"); + return 0; + } + + for (CellInfo cellInfo : cellInfoList) { + if (!cellInfo.isRegistered()) { + continue; + } + if (cellInfo instanceof CellInfoGsm) { + return TelephonyManager.NETWORK_TYPE_GSM; + } else if (cellInfo instanceof CellInfoWcdma) { + return TelephonyManager.NETWORK_TYPE_UMTS; + } else if (cellInfo instanceof CellInfoLte) { + return TelephonyManager.NETWORK_TYPE_LTE; + } else if (cellInfo instanceof CellInfoNr) { + return TelephonyManager.NETWORK_TYPE_NR; + } + } + return TelephonyManager.NETWORK_TYPE_UNKNOWN; + } + + /* Determines if this subscription is in an active call */ + private boolean isOnCall() { + return mCallState != TelephonyManager.CALL_STATE_IDLE; + } + + /** + * IMS and Emergency are not allowed to retry with initial attach during call to keep call + * continuity. Other APNs like XCAP and MMS are allowed to retry with initial attach + * regardless of the call state. + */ + private boolean shouldRetryWithInitialAttachForHandoverRequest( + String apn, TunnelState tunnelState) { + boolean isOnImsOrEmergencyCall = tunnelState.getIsImsOrEmergency() && isOnCall(); + return tunnelState.getIsHandover() + && !isOnImsOrEmergencyCall + && ErrorPolicyManager.getInstance(mContext, getSlotIndex()) + .shouldRetryWithInitialAttach(apn); } /** @@ -1160,109 +1098,801 @@ public class IwlanDataService extends DataService { public void close() { // TODO: call epdgtunnelmanager.releaseInstance or equivalent mIwlanDataService.removeDataServiceProvider(this); - IwlanEventListener.getInstance(mContext, getSlotIndex()).removeEventListener(mHandler); - mHandlerThread.quit(); + IwlanEventListener iwlanEventListener = + IwlanEventListener.getInstance(mContext, getSlotIndex()); + iwlanEventListener.removeEventListener(getIwlanDataServiceHandler()); + iwlanEventListener.unregisterContentObserver(); } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("---- IwlanDataServiceProvider[" + getSlotIndex() + "] ----"); boolean isDDS = IwlanHelper.isDefaultDataSlot(mContext, getSlotIndex()); boolean isCSTEnabled = IwlanHelper.isCrossSimCallingEnabled(mContext, getSlotIndex()); - pw.println("isDefaultDataSub: " + isDDS + " isCrossSimEnabled: " + isCSTEnabled); + pw.println( + "isDefaultDataSlot: " + + isDDS + + "subID: " + + IwlanHelper.getSubId(mContext, getSlotIndex()) + + " mConnectedDataSub: " + + mConnectedDataSub + + " isCrossSimEnabled: " + + isCSTEnabled); pw.println( "isNetworkConnected: " - + isNetworkConnected(isDDS, isCSTEnabled) + + isNetworkConnected( + isActiveDataOnOtherSub(getSlotIndex()), isCSTEnabled) + " Wfc enabled: " + mWfcEnabled); - synchronized (mTunnelStateForApn) { - for (Map.Entry<String, TunnelState> entry : mTunnelStateForApn.entrySet()) { - pw.println("Tunnel state for APN: " + entry.getKey()); - pw.println(entry.getValue()); - } + for (Map.Entry<String, TunnelState> entry : mTunnelStateForApn.entrySet()) { + pw.println("Tunnel state for APN: " + entry.getKey()); + pw.println(entry.getValue()); } pw.println(mTunnelStats); - EpdgTunnelManager.getInstance(mContext, getSlotIndex()).dump(fd, pw, args); - ErrorPolicyManager.getInstance(mContext, getSlotIndex()).dump(fd, pw, args); + EpdgTunnelManager.getInstance(mContext, getSlotIndex()).dump(pw); + ErrorPolicyManager.getInstance(mContext, getSlotIndex()).dump(pw); pw.println("-------------------------------------"); } + + @VisibleForTesting + public void setCalendar(Calendar c) { + mCalendar = c; + } + } + + private final class IwlanDataServiceHandler extends Handler { + private final String TAG = IwlanDataServiceHandler.class.getSimpleName(); + + @Override + public void handleMessage(Message msg) { + Log.d(TAG, "msg.what = " + eventToString(msg.what)); + + String apnName; + IwlanDataServiceProvider iwlanDataServiceProvider; + IwlanDataServiceProvider.TunnelState tunnelState; + DataServiceCallback callback; + int reason; + int slotId; + int retryTimeMillis; + int errorCause; + MetricsAtom metricsAtom; + + switch (msg.what) { + case EVENT_TUNNEL_OPENED: + TunnelOpenedData tunnelOpenedData = (TunnelOpenedData) msg.obj; + iwlanDataServiceProvider = tunnelOpenedData.mIwlanDataServiceProvider; + apnName = tunnelOpenedData.mApnName; + TunnelLinkProperties tunnelLinkProperties = + tunnelOpenedData.mTunnelLinkProperties; + + tunnelState = iwlanDataServiceProvider.mTunnelStateForApn.get(apnName); + // tunnelstate should not be null, design violation. + // if its null, we should crash and debug. + tunnelState.setTunnelLinkProperties(tunnelLinkProperties); + tunnelState.setState(IwlanDataServiceProvider.TunnelState.TUNNEL_UP); + iwlanDataServiceProvider.mTunnelStats.reportTunnelSetupSuccess( + apnName, tunnelState); + + iwlanDataServiceProvider.deliverCallback( + IwlanDataServiceProvider.CALLBACK_TYPE_SETUP_DATACALL_COMPLETE, + DataServiceCallback.RESULT_SUCCESS, + tunnelState.getDataServiceCallback(), + iwlanDataServiceProvider.apnTunnelStateToDataCallResponse(apnName)); + break; + + case EVENT_TUNNEL_CLOSED: + TunnelClosedData tunnelClosedData = (TunnelClosedData) msg.obj; + iwlanDataServiceProvider = tunnelClosedData.mIwlanDataServiceProvider; + apnName = tunnelClosedData.mApnName; + IwlanError iwlanError = tunnelClosedData.mIwlanError; + + tunnelState = iwlanDataServiceProvider.mTunnelStateForApn.get(apnName); + + if (tunnelState == null) { + // On a successful handover to EUTRAN, the NW may initiate an IKE DEL before + // the UE initiates a deactivateDataCall(). There may be a race condition + // where the deactivateDataCall() arrives immediately before + // IwlanDataService receives EVENT_TUNNEL_CLOSED (and clears TunnelState). + // Even though there is no tunnel, EpdgTunnelManager will still process the + // bringdown request and send back an onClosed() to ensure state coherence. + if (iwlanError.getErrorType() != IwlanError.TUNNEL_NOT_FOUND) { + Log.w( + TAG, + "Tunnel state does not exist! Unexpected IwlanError: " + + iwlanError); + } + break; + } + + iwlanDataServiceProvider.mTunnelStats.reportTunnelDown(apnName, tunnelState); + iwlanDataServiceProvider.mTunnelStateForApn.remove(apnName); + metricsAtom = iwlanDataServiceProvider.mMetricsAtomForApn.get(apnName); + + if (tunnelState.getState() + == IwlanDataServiceProvider.TunnelState.TUNNEL_IN_BRINGUP + || tunnelState.getState() + == IwlanDataServiceProvider.TunnelState + .TUNNEL_IN_FORCE_CLEAN_WAS_IN_BRINGUP) { + DataCallResponse.Builder respBuilder = new DataCallResponse.Builder(); + respBuilder + .setId(apnName.hashCode()) + .setProtocolType(tunnelState.getProtocolType()); + + if (iwlanDataServiceProvider.shouldRetryWithInitialAttachForHandoverRequest( + apnName, tunnelState)) { + respBuilder.setHandoverFailureMode( + DataCallResponse + .HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL); + metricsAtom.setHandoverFailureMode( + DataCallResponse + .HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL); + } else if (tunnelState.getIsHandover()) { + respBuilder.setHandoverFailureMode( + DataCallResponse + .HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER); + metricsAtom.setHandoverFailureMode( + DataCallResponse + .HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER); + } + + errorCause = + ErrorPolicyManager.getInstance( + mContext, iwlanDataServiceProvider.getSlotIndex()) + .getDataFailCause(apnName); + if (errorCause != DataFailCause.NONE) { + respBuilder.setCause(errorCause); + metricsAtom.setDataCallFailCause(errorCause); + + retryTimeMillis = + (int) + ErrorPolicyManager.getInstance( + mContext, + iwlanDataServiceProvider.getSlotIndex()) + .getCurrentRetryTimeMs(apnName); + respBuilder.setRetryDurationMillis(retryTimeMillis); + metricsAtom.setRetryDurationMillis(retryTimeMillis); + } else { + // TODO(b/265215349): Use a different DataFailCause for scenario where + // tunnel in bringup is closed or force-closed without error. + respBuilder.setCause(DataFailCause.IWLAN_NETWORK_FAILURE); + metricsAtom.setDataCallFailCause(DataFailCause.IWLAN_NETWORK_FAILURE); + respBuilder.setRetryDurationMillis(5000); + metricsAtom.setRetryDurationMillis(5000); + } + + // Record setup result for the Metrics + metricsAtom.setSetupRequestResult(DataServiceCallback.RESULT_SUCCESS); + metricsAtom.setIwlanError(iwlanError.getErrorType()); + + metricsAtom.setIwlanErrorWrappedClassnameAndStack(iwlanError); + + metricsAtom.setTunnelState(tunnelState.getState()); + metricsAtom.setMessageId( + IwlanStatsLog.IWLAN_SETUP_DATA_CALL_RESULT_REPORTED); + + iwlanDataServiceProvider.deliverCallback( + IwlanDataServiceProvider.CALLBACK_TYPE_SETUP_DATACALL_COMPLETE, + DataServiceCallback.RESULT_SUCCESS, + tunnelState.getDataServiceCallback(), + respBuilder.build()); + return; + } + + // iwlan service triggered teardown + if (tunnelState.getState() + == IwlanDataServiceProvider.TunnelState.TUNNEL_IN_BRINGDOWN) { + + // IO exception happens when IKE library fails to retransmit requests. + // This can happen for multiple reasons: + // 1. Network disconnection due to wifi off. + // 2. Epdg server does not respond. + // 3. Socket send/receive fails. + // Ignore this during tunnel bring down. + if (iwlanError.getErrorType() != IwlanError.NO_ERROR + && iwlanError.getErrorType() + != IwlanError.IKE_INTERNAL_IO_EXCEPTION) { + Log.e(TAG, "Unexpected error during tunnel bring down: " + iwlanError); + } + + iwlanDataServiceProvider.deliverCallback( + IwlanDataServiceProvider.CALLBACK_TYPE_DEACTIVATE_DATACALL_COMPLETE, + DataServiceCallback.RESULT_SUCCESS, + tunnelState.getDataServiceCallback(), + null); + + return; + } + + // just update list of data calls. No way to send error up + iwlanDataServiceProvider.notifyDataCallListChanged( + iwlanDataServiceProvider.getCallList()); + + // Report IwlanPdnDisconnectedReason due to the disconnection is neither for + // SETUP_DATA_CALL nor DEACTIVATE_DATA_CALL request. + metricsAtom.setDataCallFailCause( + ErrorPolicyManager.getInstance( + mContext, iwlanDataServiceProvider.getSlotIndex()) + .getDataFailCause(apnName)); + + WifiManager wifiManager = mContext.getSystemService(WifiManager.class); + if (wifiManager == null) { + Log.e(TAG, "Could not find wifiManager"); + return; + } + + WifiInfo wifiInfo = wifiManager.getConnectionInfo(); + if (wifiInfo == null) { + Log.e(TAG, "wifiInfo is null"); + return; + } + + metricsAtom.setWifiSignalValue(wifiInfo.getRssi()); + metricsAtom.setMessageId(IwlanStatsLog.IWLAN_PDN_DISCONNECTED_REASON_REPORTED); + break; + + case IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT: + iwlanDataServiceProvider = + (IwlanDataServiceProvider) getDataServiceProvider(msg.arg1); + + iwlanDataServiceProvider.mCarrierConfigReady = true; + iwlanDataServiceProvider.dnsPrefetchCheck(); + break; + + case IwlanEventListener.CARRIER_CONFIG_UNKNOWN_CARRIER_EVENT: + iwlanDataServiceProvider = + (IwlanDataServiceProvider) getDataServiceProvider(msg.arg1); + + iwlanDataServiceProvider.mCarrierConfigReady = false; + break; + + case IwlanEventListener.WIFI_CALLING_ENABLE_EVENT: + iwlanDataServiceProvider = + (IwlanDataServiceProvider) getDataServiceProvider(msg.arg1); + + iwlanDataServiceProvider.mWfcEnabled = true; + iwlanDataServiceProvider.dnsPrefetchCheck(); + break; + + case IwlanEventListener.WIFI_CALLING_DISABLE_EVENT: + iwlanDataServiceProvider = + (IwlanDataServiceProvider) getDataServiceProvider(msg.arg1); + + iwlanDataServiceProvider.mWfcEnabled = false; + break; + + case IwlanEventListener.CROSS_SIM_CALLING_ENABLE_EVENT: + iwlanDataServiceProvider = + (IwlanDataServiceProvider) getDataServiceProvider(msg.arg1); + iwlanDataServiceProvider.updateNetwork(sNetwork, sLinkProperties); + break; + + case IwlanEventListener.CELLINFO_CHANGED_EVENT: + List<CellInfo> cellInfolist = (List<CellInfo>) msg.obj; + iwlanDataServiceProvider = + (IwlanDataServiceProvider) getDataServiceProvider(msg.arg1); + + if (cellInfolist != null + && iwlanDataServiceProvider.isRegisteredCellInfoChanged(cellInfolist)) { + int[] addrResolutionMethods = + IwlanHelper.getConfig( + CarrierConfigManager.Iwlan + .KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY, + mContext, + iwlanDataServiceProvider.getSlotIndex()); + for (int addrResolutionMethod : addrResolutionMethods) { + if (addrResolutionMethod + == CarrierConfigManager.Iwlan.EPDG_ADDRESS_CELLULAR_LOC) { + iwlanDataServiceProvider.dnsPrefetchCheck(); + } + } + } + break; + + case IwlanEventListener.CALL_STATE_CHANGED_EVENT: + iwlanDataServiceProvider = + (IwlanDataServiceProvider) getDataServiceProvider(msg.arg1); + + iwlanDataServiceProvider.mCallState = msg.arg2; + break; + + case EVENT_SETUP_DATA_CALL: + SetupDataCallData setupDataCallData = (SetupDataCallData) msg.obj; + int accessNetworkType = setupDataCallData.mAccessNetworkType; + @NonNull DataProfile dataProfile = setupDataCallData.mDataProfile; + boolean isRoaming = setupDataCallData.mIsRoaming; + reason = setupDataCallData.mReason; + LinkProperties linkProperties = setupDataCallData.mLinkProperties; + @IntRange(from = 0, to = 15) + int pduSessionId = setupDataCallData.mPduSessionId; + callback = setupDataCallData.mCallback; + iwlanDataServiceProvider = setupDataCallData.mIwlanDataServiceProvider; + + if ((accessNetworkType != AccessNetworkType.IWLAN) + || (dataProfile == null) + || (linkProperties == null + && reason == DataService.REQUEST_REASON_HANDOVER)) { + + iwlanDataServiceProvider.deliverCallback( + IwlanDataServiceProvider.CALLBACK_TYPE_SETUP_DATACALL_COMPLETE, + DataServiceCallback.RESULT_ERROR_INVALID_ARG, + callback, + null); + return; + } + + slotId = iwlanDataServiceProvider.getSlotIndex(); + boolean isCSTEnabled = IwlanHelper.isCrossSimCallingEnabled(mContext, slotId); + boolean networkConnected = + isNetworkConnected(isActiveDataOnOtherSub(slotId), isCSTEnabled); + Log.d( + TAG + "[" + slotId + "]", + "isDds: " + + IwlanHelper.isDefaultDataSlot(mContext, slotId) + + ", isActiveDataOnOtherSub: " + + isActiveDataOnOtherSub(slotId) + + ", isCstEnabled: " + + isCSTEnabled + + ", transport: " + + sDefaultDataTransport); + + if (!networkConnected) { + iwlanDataServiceProvider.deliverCallback( + IwlanDataServiceProvider.CALLBACK_TYPE_SETUP_DATACALL_COMPLETE, + 5 /* DataServiceCallback.RESULT_ERROR_TEMPORARILY_UNAVAILABLE + */, + callback, + null); + return; + } + + tunnelState = + iwlanDataServiceProvider.mTunnelStateForApn.get( + dataProfile.getApnSetting().getApnName()); + + // Return the existing PDN if the pduSessionId is the same and the tunnel + // state is + // TUNNEL_UP. + if (tunnelState != null) { + if (tunnelState.getPduSessionId() == pduSessionId + && tunnelState.getState() + == IwlanDataServiceProvider.TunnelState.TUNNEL_UP) { + Log.w( + TAG + "[" + slotId + "]", + "The tunnel for " + + dataProfile.getApnSetting().getApnName() + + " already exists."); + iwlanDataServiceProvider.deliverCallback( + IwlanDataServiceProvider.CALLBACK_TYPE_SETUP_DATACALL_COMPLETE, + DataServiceCallback.RESULT_SUCCESS, + callback, + iwlanDataServiceProvider.apnTunnelStateToDataCallResponse( + dataProfile.getApnSetting().getApnName())); + } else { + Log.e( + TAG + "[" + slotId + "]", + "Force close the existing PDN. pduSessionId = " + + tunnelState.getPduSessionId() + + " Tunnel State = " + + tunnelState.getState()); + iwlanDataServiceProvider + .getTunnelManager() + .closeTunnel( + dataProfile.getApnSetting().getApnName(), + true /* forceClose */, + iwlanDataServiceProvider.getIwlanTunnelCallback(), + iwlanDataServiceProvider.getIwlanTunnelMetrics()); + iwlanDataServiceProvider.deliverCallback( + IwlanDataServiceProvider.CALLBACK_TYPE_SETUP_DATACALL_COMPLETE, + 5 /* DataServiceCallback + .RESULT_ERROR_TEMPORARILY_UNAVAILABLE */, + callback, + null); + } + return; + } + + TunnelSetupRequest.Builder tunnelReqBuilder = + TunnelSetupRequest.builder() + .setApnName(dataProfile.getApnSetting().getApnName()) + .setIsRoaming(isRoaming) + .setPduSessionId(pduSessionId) + .setApnIpProtocol( + isRoaming + ? dataProfile + .getApnSetting() + .getRoamingProtocol() + : dataProfile.getApnSetting().getProtocol()); + + if (reason == DataService.REQUEST_REASON_HANDOVER) { + // for now assume that, at max, only one address of eachtype (v4/v6). + // TODO: Check if multiple ips can be sent in ike tunnel setup + for (LinkAddress lAddr : linkProperties.getLinkAddresses()) { + if (lAddr.isIpv4()) { + tunnelReqBuilder.setSrcIpv4Address(lAddr.getAddress()); + } else if (lAddr.isIpv6()) { + tunnelReqBuilder.setSrcIpv6Address(lAddr.getAddress()); + tunnelReqBuilder.setSrcIpv6AddressPrefixLength( + lAddr.getPrefixLength()); + } + } + } + + int apnTypeBitmask = dataProfile.getApnSetting().getApnTypeBitmask(); + boolean isIMS = hasApnTypes(apnTypeBitmask, ApnSetting.TYPE_IMS); + boolean isEmergency = hasApnTypes(apnTypeBitmask, ApnSetting.TYPE_EMERGENCY); + tunnelReqBuilder.setRequestPcscf(isIMS || isEmergency); + tunnelReqBuilder.setIsEmergency(isEmergency); + + iwlanDataServiceProvider.setTunnelState( + dataProfile, + callback, + IwlanDataServiceProvider.TunnelState.TUNNEL_IN_BRINGUP, + null, + (reason == DataService.REQUEST_REASON_HANDOVER), + pduSessionId, + isIMS || isEmergency); + + boolean result = + iwlanDataServiceProvider + .getTunnelManager() + .bringUpTunnel( + tunnelReqBuilder.build(), + iwlanDataServiceProvider.getIwlanTunnelCallback(), + iwlanDataServiceProvider.getIwlanTunnelMetrics()); + Log.d(TAG + "[" + slotId + "]", "bringup Tunnel with result:" + result); + if (!result) { + iwlanDataServiceProvider.deliverCallback( + IwlanDataServiceProvider.CALLBACK_TYPE_SETUP_DATACALL_COMPLETE, + DataServiceCallback.RESULT_ERROR_INVALID_ARG, + callback, + null); + return; + } + break; + + case EVENT_DEACTIVATE_DATA_CALL: + DeactivateDataCallData deactivateDataCallData = + (DeactivateDataCallData) msg.obj; + iwlanDataServiceProvider = deactivateDataCallData.mIwlanDataServiceProvider; + callback = deactivateDataCallData.mCallback; + reason = deactivateDataCallData.mReason; + + int cid = deactivateDataCallData.mCid; + slotId = iwlanDataServiceProvider.getSlotIndex(); + boolean isNetworkLost = + !isNetworkConnected( + isActiveDataOnOtherSub(slotId), + IwlanHelper.isCrossSimCallingEnabled(mContext, slotId)); + boolean isHandOutSuccessful = (reason == REQUEST_REASON_HANDOVER); + + for (String apn : iwlanDataServiceProvider.mTunnelStateForApn.keySet()) { + if (apn.hashCode() == cid) { + // No need to check state since dataconnection in framework serializes + // setup and deactivate calls using callId/cid. + iwlanDataServiceProvider + .mTunnelStateForApn + .get(apn) + .setState( + IwlanDataServiceProvider.TunnelState + .TUNNEL_IN_BRINGDOWN); + iwlanDataServiceProvider + .mTunnelStateForApn + .get(apn) + .setDataServiceCallback(callback); + + // According to the handover procedure in 3GPP specifications (TS 23.402 + // clause 8.6.1 for S1; TS 23.502 clause 4.11.4.1 for N1), if the PDN is + // handed out to another RAT, the IKE tunnel over ePDG SHOULD be + // released by the network. Thus, UE just released the tunnel locally. + iwlanDataServiceProvider + .getTunnelManager() + .closeTunnel( + apn, + isNetworkLost || isHandOutSuccessful /* forceClose */, + iwlanDataServiceProvider.getIwlanTunnelCallback(), + iwlanDataServiceProvider.getIwlanTunnelMetrics()); + return; + } + } + + iwlanDataServiceProvider.deliverCallback( + IwlanDataServiceProvider.CALLBACK_TYPE_DEACTIVATE_DATACALL_COMPLETE, + DataServiceCallback.RESULT_ERROR_INVALID_ARG, + callback, + null); + break; + + case EVENT_DATA_CALL_LIST_REQUEST: + DataCallRequestData dataCallRequestData = (DataCallRequestData) msg.obj; + callback = dataCallRequestData.mCallback; + iwlanDataServiceProvider = dataCallRequestData.mIwlanDataServiceProvider; + + iwlanDataServiceProvider.deliverCallback( + IwlanDataServiceProvider.CALLBACK_TYPE_GET_DATACALL_LIST_COMPLETE, + DataServiceCallback.RESULT_SUCCESS, + callback, + null); + break; + + case EVENT_FORCE_CLOSE_TUNNEL: + for (IwlanDataServiceProvider dp : sIwlanDataServiceProviders.values()) { + dp.forceCloseTunnels(); + } + break; + + case EVENT_ADD_DATA_SERVICE_PROVIDER: + iwlanDataServiceProvider = (IwlanDataServiceProvider) msg.obj; + addIwlanDataServiceProvider(iwlanDataServiceProvider); + break; + + case EVENT_REMOVE_DATA_SERVICE_PROVIDER: + iwlanDataServiceProvider = (IwlanDataServiceProvider) msg.obj; + + slotId = iwlanDataServiceProvider.getSlotIndex(); + IwlanDataServiceProvider dsp = sIwlanDataServiceProviders.remove(slotId); + if (dsp == null) { + Log.w(TAG + "[" + slotId + "]", "No DataServiceProvider exists for slot!"); + } + + if (sIwlanDataServiceProviders.isEmpty()) { + deinitNetworkCallback(); + } + break; + + case EVENT_TUNNEL_OPENED_METRICS: + OnOpenedMetrics openedMetricsData = (OnOpenedMetrics) msg.obj; + iwlanDataServiceProvider = openedMetricsData.getIwlanDataServiceProvider(); + apnName = openedMetricsData.getApnName(); + + // Record setup result for the Metrics + metricsAtom = iwlanDataServiceProvider.mMetricsAtomForApn.get(apnName); + tunnelState = iwlanDataServiceProvider.mTunnelStateForApn.get(apnName); + metricsAtom.setSetupRequestResult(DataServiceCallback.RESULT_SUCCESS); + metricsAtom.setIwlanError(IwlanError.NO_ERROR); + metricsAtom.setDataCallFailCause(DataFailCause.NONE); + metricsAtom.setTunnelState(tunnelState.getState()); + metricsAtom.setHandoverFailureMode(-1); + metricsAtom.setRetryDurationMillis(0); + metricsAtom.setMessageId(IwlanStatsLog.IWLAN_SETUP_DATA_CALL_RESULT_REPORTED); + metricsAtom.setEpdgServerAddress(openedMetricsData.getEpdgServerAddress()); + metricsAtom.setProcessingDurationMillis( + (int) + (System.currentTimeMillis() + - iwlanDataServiceProvider.mProcessingStartTime)); + metricsAtom.setEpdgServerSelectionDurationMillis( + openedMetricsData.getEpdgServerSelectionDuration()); + metricsAtom.setIkeTunnelEstablishmentDurationMillis( + openedMetricsData.getIkeTunnelEstablishmentDuration()); + + metricsAtom.sendMetricsData(); + break; + + case EVENT_TUNNEL_CLOSED_METRICS: + OnClosedMetrics closedMetricsData = (OnClosedMetrics) msg.obj; + iwlanDataServiceProvider = closedMetricsData.getIwlanDataServiceProvider(); + apnName = closedMetricsData.getApnName(); + + metricsAtom = iwlanDataServiceProvider.mMetricsAtomForApn.get(apnName); + if (metricsAtom == null) { + Log.w(TAG, "EVENT_TUNNEL_CLOSED_METRICS: MetricsAtom is null!"); + break; + } + metricsAtom.setEpdgServerAddress(closedMetricsData.getEpdgServerAddress()); + metricsAtom.setProcessingDurationMillis( + iwlanDataServiceProvider.mProcessingStartTime > 0 + ? (int) + (System.currentTimeMillis() + - iwlanDataServiceProvider.mProcessingStartTime) + : 0); + metricsAtom.setEpdgServerSelectionDurationMillis( + closedMetricsData.getEpdgServerSelectionDuration()); + metricsAtom.setIkeTunnelEstablishmentDurationMillis( + closedMetricsData.getIkeTunnelEstablishmentDuration()); + + metricsAtom.sendMetricsData(); + iwlanDataServiceProvider.mMetricsAtomForApn.remove(apnName); + break; + + default: + throw new IllegalStateException("Unexpected value: " + msg.what); + } + } + + IwlanDataServiceHandler(Looper looper) { + super(looper); + } + } + + private static final class TunnelOpenedData { + final String mApnName; + final TunnelLinkProperties mTunnelLinkProperties; + final IwlanDataServiceProvider mIwlanDataServiceProvider; + + private TunnelOpenedData( + String apnName, + TunnelLinkProperties tunnelLinkProperties, + IwlanDataServiceProvider dsp) { + mApnName = apnName; + mTunnelLinkProperties = tunnelLinkProperties; + mIwlanDataServiceProvider = dsp; + } + } + + private static final class TunnelClosedData { + final String mApnName; + final IwlanError mIwlanError; + final IwlanDataServiceProvider mIwlanDataServiceProvider; + + private TunnelClosedData( + String apnName, IwlanError iwlanError, IwlanDataServiceProvider dsp) { + mApnName = apnName; + mIwlanError = iwlanError; + mIwlanDataServiceProvider = dsp; + } + } + + private static final class SetupDataCallData { + final int mAccessNetworkType; + @NonNull final DataProfile mDataProfile; + final boolean mIsRoaming; + final boolean mAllowRoaming; + final int mReason; + @Nullable final LinkProperties mLinkProperties; + + @IntRange(from = 0, to = 15) + final int mPduSessionId; + + @Nullable final NetworkSliceInfo mSliceInfo; + @Nullable final TrafficDescriptor mTrafficDescriptor; + final boolean mMatchAllRuleAllowed; + @NonNull final DataServiceCallback mCallback; + final IwlanDataServiceProvider mIwlanDataServiceProvider; + + private SetupDataCallData( + int accessNetworkType, + DataProfile dataProfile, + boolean isRoaming, + boolean allowRoaming, + int reason, + LinkProperties linkProperties, + int pduSessionId, + NetworkSliceInfo sliceInfo, + TrafficDescriptor trafficDescriptor, + boolean matchAllRuleAllowed, + DataServiceCallback callback, + IwlanDataServiceProvider dsp) { + mAccessNetworkType = accessNetworkType; + mDataProfile = dataProfile; + mIsRoaming = isRoaming; + mAllowRoaming = allowRoaming; + mReason = reason; + mLinkProperties = linkProperties; + mPduSessionId = pduSessionId; + mSliceInfo = sliceInfo; + mTrafficDescriptor = trafficDescriptor; + mMatchAllRuleAllowed = matchAllRuleAllowed; + mCallback = callback; + mIwlanDataServiceProvider = dsp; + } + } + + private static final class DeactivateDataCallData { + final int mCid; + final int mReason; + final DataServiceCallback mCallback; + final IwlanDataServiceProvider mIwlanDataServiceProvider; + + private DeactivateDataCallData( + int cid, int reason, DataServiceCallback callback, IwlanDataServiceProvider dsp) { + mCid = cid; + mReason = reason; + mCallback = callback; + mIwlanDataServiceProvider = dsp; + } + } + + private static final class DataCallRequestData { + final DataServiceCallback mCallback; + final IwlanDataServiceProvider mIwlanDataServiceProvider; + + private DataCallRequestData(DataServiceCallback callback, IwlanDataServiceProvider dsp) { + mCallback = callback; + mIwlanDataServiceProvider = dsp; + } + } + + static int getConnectedDataSub(NetworkCapabilities networkCapabilities) { + int connectedDataSub = INVALID_SUB_ID; + NetworkSpecifier specifier = networkCapabilities.getNetworkSpecifier(); + TransportInfo transportInfo = networkCapabilities.getTransportInfo(); + + if (specifier instanceof TelephonyNetworkSpecifier) { + connectedDataSub = ((TelephonyNetworkSpecifier) specifier).getSubscriptionId(); + } else if (transportInfo instanceof VcnTransportInfo) { + connectedDataSub = ((VcnTransportInfo) transportInfo).getSubId(); + } + return connectedDataSub; + } + + static void setConnectedDataSub(int subId) { + mConnectedDataSub = subId; } @VisibleForTesting - static synchronized boolean isNetworkConnected(boolean isDds, boolean isCstEnabled) { - if (!isDds && isCstEnabled) { - // Only Non-DDS sub with CST enabled, can use any transport. + static boolean isActiveDataOnOtherSub(int slotId) { + int subId = IwlanHelper.getSubId(mContext, slotId); + return mConnectedDataSub != INVALID_SUB_ID && subId != mConnectedDataSub; + } + + @VisibleForTesting + static boolean isNetworkConnected(boolean isActiveDataOnOtherSub, boolean isCstEnabled) { + if (isActiveDataOnOtherSub && isCstEnabled) { + // For cross-SIM IWLAN (Transport.MOBILE), an active data PDN must be maintained on the + // other subscription. + if (sNetworkConnected && (sDefaultDataTransport != Transport.MOBILE)) { + Log.e(TAG, "Internet is on other slot, but default transport is not MOBILE!"); + } return sNetworkConnected; } else { - // For all other cases, only wifi transport can be used. + // For all other cases, only Transport.WIFI can be used. return ((sDefaultDataTransport == Transport.WIFI) && sNetworkConnected); } } - @VisibleForTesting /* Note: this api should have valid transport if networkConnected==true */ - // Only synchronize on IwlanDataService.class for changes being made to static variables - // Calls to DataServiceProvider object methods (or any objects in the future) should - // not be made within synchronized block protected by IwlanDataService.class static void setNetworkConnected( - boolean networkConnected, Network network, Transport transport) { + boolean networkConnected, @NonNull Network network, Transport transport) { boolean hasNetworkChanged = false; boolean hasTransportChanged = false; boolean hasNetworkConnectedChanged = false; - synchronized (IwlanDataService.class) { - if (sNetworkConnected == networkConnected - && network.equals(sNetwork) - && sDefaultDataTransport == transport) { - // Nothing changed - return; - } - - // safety check - if (networkConnected && transport == Transport.UNSPECIFIED_NETWORK) { - Log.e(TAG, "setNetworkConnected: Network connected but transport unspecified"); - return; - } + if (sNetworkConnected == networkConnected + && network.equals(sNetwork) + && sDefaultDataTransport == transport) { + // Nothing changed + return; + } - if (!network.equals(sNetwork)) { - Log.e(TAG, "setNetworkConnected NW changed from: " + sNetwork + " TO: " + network); - hasNetworkChanged = true; - } + // safety check + if (networkConnected && transport == Transport.UNSPECIFIED_NETWORK) { + Log.e(TAG, "setNetworkConnected: Network connected but transport unspecified"); + return; + } - if (transport != sDefaultDataTransport) { - Log.d( - TAG, - "Transport was changed from " - + sDefaultDataTransport.name() - + " to " - + transport.name()); - hasTransportChanged = true; - } + if (!network.equals(sNetwork)) { + Log.e(TAG, "System default network changed from: " + sNetwork + " TO: " + network); + hasNetworkChanged = true; + } - if (sNetworkConnected != networkConnected) { - Log.d( - TAG, - "Network connected state change from " - + sNetworkConnected - + " to " - + networkConnected); - hasNetworkConnectedChanged = true; - } + if (transport != sDefaultDataTransport) { + Log.d( + TAG, + "Transport was changed from " + + sDefaultDataTransport.name() + + " to " + + transport.name()); + hasTransportChanged = true; + } - sNetworkConnected = networkConnected; - sDefaultDataTransport = transport; - sNetwork = network; - if (!networkConnected) { - // reset link protocol type - sLinkProtocolType = LinkProtocolType.UNKNOWN; - } + if (sNetworkConnected != networkConnected) { + Log.d( + TAG, + "Network connected state change from " + + sNetworkConnected + + " to " + + networkConnected); + hasNetworkConnectedChanged = true; } + sNetworkConnected = networkConnected; + sDefaultDataTransport = transport; + sNetwork = network; + if (networkConnected) { if (hasTransportChanged) { // Perform forceClose for tunnels in bringdown. // let framework handle explicit teardown - for (IwlanDataServiceProvider dp : sIwlanDataServiceProviderList) { + for (IwlanDataServiceProvider dp : sIwlanDataServiceProviders.values()) { dp.forceCloseTunnelsInDeactivatingState(); } } @@ -1272,14 +1902,18 @@ public class IwlanDataService extends DataService { } // only prefetch dns and updateNetwork if Network has changed if (hasNetworkChanged) { - for (IwlanDataServiceProvider dp : sIwlanDataServiceProviderList) { + ConnectivityManager connectivityManager = + mContext.getSystemService(ConnectivityManager.class); + LinkProperties linkProperties = connectivityManager.getLinkProperties(network); + sLinkProperties = linkProperties; + for (IwlanDataServiceProvider dp : sIwlanDataServiceProviders.values()) { dp.dnsPrefetchCheck(); - dp.updateNetwork(sNetwork); + dp.updateNetwork(sNetwork, linkProperties); } IwlanHelper.updateCountryCodeWhenNetworkConnected(); } } else { - for (IwlanDataServiceProvider dp : sIwlanDataServiceProviderList) { + for (IwlanDataServiceProvider dp : sIwlanDataServiceProviders.values()) { // once network is disconnected, even NAT KA offload fails // But we should still let framework do an explicit teardown // so as to not affect an ongoing handover @@ -1289,48 +1923,6 @@ public class IwlanDataService extends DataService { } } - static boolean isLinkProtocolTypeChanged(LinkProperties linkProperties) { - boolean hasIPV4 = false; - boolean hasIPV6 = false; - - LinkProtocolType linkProtocolType = null; - if (linkProperties != null) { - for (LinkAddress linkAddress : linkProperties.getLinkAddresses()) { - InetAddress inetaddr = linkAddress.getAddress(); - // skip linklocal and loopback addresses - if (!inetaddr.isLoopbackAddress() && !inetaddr.isLinkLocalAddress()) { - if (inetaddr instanceof Inet4Address) { - hasIPV4 = true; - } else if (inetaddr instanceof Inet6Address) { - hasIPV6 = true; - } - } - } - - if (hasIPV4 && hasIPV6) { - linkProtocolType = LinkProtocolType.IPV4V6; - } else if (hasIPV4) { - linkProtocolType = LinkProtocolType.IPV4; - } else if (hasIPV6) { - linkProtocolType = LinkProtocolType.IPV6; - } - - if (sLinkProtocolType != linkProtocolType) { - Log.d( - TAG, - "LinkProtocolType was changed from " - + sLinkProtocolType - + " to " - + linkProtocolType); - sLinkProtocolType = linkProtocolType; - return true; - } - return false; - } - Log.w(TAG, "linkProperties is NULL."); - return false; - } - /** * Get the DataServiceProvider associated with the slotId * @@ -1338,16 +1930,7 @@ public class IwlanDataService extends DataService { * @return DataService.DataServiceProvider associated with the slot */ public static DataService.DataServiceProvider getDataServiceProvider(int slotId) { - DataServiceProvider ret = null; - if (!sIwlanDataServiceProviderList.isEmpty()) { - for (IwlanDataServiceProvider provider : sIwlanDataServiceProviderList) { - if (provider.getSlotIndex() == slotId) { - ret = provider; - break; - } - } - } - return ret; + return sIwlanDataServiceProviders.get(slotId); } public static Context getContext() { @@ -1360,42 +1943,55 @@ public class IwlanDataService extends DataService { Log.d(TAG, "Creating provider for " + slotIndex); if (mNetworkMonitorCallback == null) { - // start monitoring network - mNetworkCallbackHandlerThread = - new HandlerThread(IwlanNetworkService.class.getSimpleName()); - mNetworkCallbackHandlerThread.start(); - Looper looper = mNetworkCallbackHandlerThread.getLooper(); - Handler handler = new Handler(looper); - - // register for default network callback + // start monitoring network and register for default network callback ConnectivityManager connectivityManager = mContext.getSystemService(ConnectivityManager.class); mNetworkMonitorCallback = new IwlanNetworkMonitorCallback(); - connectivityManager.registerDefaultNetworkCallback(mNetworkMonitorCallback, handler); + if (connectivityManager != null) { + connectivityManager.registerSystemDefaultNetworkCallback( + mNetworkMonitorCallback, getIwlanDataServiceHandler()); + } Log.d(TAG, "Registered with Connectivity Service"); } IwlanDataServiceProvider dp = new IwlanDataServiceProvider(slotIndex, this); - addIwlanDataServiceProvider(dp); + + getIwlanDataServiceHandler() + .sendMessage( + getIwlanDataServiceHandler() + .obtainMessage(EVENT_ADD_DATA_SERVICE_PROVIDER, dp)); return dp; } public void removeDataServiceProvider(IwlanDataServiceProvider dp) { - sIwlanDataServiceProviderList.remove(dp); - if (sIwlanDataServiceProviderList.isEmpty()) { - // deinit network related stuff - ConnectivityManager connectivityManager = - mContext.getSystemService(ConnectivityManager.class); - connectivityManager.unregisterNetworkCallback(mNetworkMonitorCallback); - mNetworkCallbackHandlerThread.quit(); // no need to quitSafely - mNetworkCallbackHandlerThread = null; - mNetworkMonitorCallback = null; - } + getIwlanDataServiceHandler() + .sendMessage( + getIwlanDataServiceHandler() + .obtainMessage(EVENT_REMOVE_DATA_SERVICE_PROVIDER, dp)); } @VisibleForTesting void addIwlanDataServiceProvider(IwlanDataServiceProvider dp) { - sIwlanDataServiceProviderList.add(dp); + int slotIndex = dp.getSlotIndex(); + if (sIwlanDataServiceProviders.containsKey(slotIndex)) { + throw new IllegalStateException( + "DataServiceProvider already exists for slot " + slotIndex); + } + sIwlanDataServiceProviders.put(slotIndex, dp); + } + + void deinitNetworkCallback() { + // deinit network related stuff + ConnectivityManager connectivityManager = + mContext.getSystemService(ConnectivityManager.class); + if (connectivityManager != null) { + connectivityManager.unregisterNetworkCallback(mNetworkMonitorCallback); + } + mNetworkMonitorCallback = null; + } + + boolean hasApnTypes(int apnTypeBitmask, int expectedApn) { + return (apnTypeBitmask & expectedApn) != 0; } @VisibleForTesting @@ -1408,9 +2004,67 @@ public class IwlanDataService extends DataService { return mNetworkMonitorCallback; } + @VisibleForTesting + @NonNull + Handler getIwlanDataServiceHandler() { + if (mIwlanDataServiceHandler == null) { + mIwlanDataServiceHandler = new IwlanDataServiceHandler(getLooper()); + } + return mIwlanDataServiceHandler; + } + + @VisibleForTesting + Looper getLooper() { + mIwlanDataServiceHandlerThread = new HandlerThread("IwlanDataServiceThread"); + mIwlanDataServiceHandlerThread.start(); + return mIwlanDataServiceHandlerThread.getLooper(); + } + + private static String eventToString(int event) { + switch (event) { + case EVENT_TUNNEL_OPENED: + return "EVENT_TUNNEL_OPENED"; + case EVENT_TUNNEL_CLOSED: + return "EVENT_TUNNEL_CLOSED"; + case EVENT_SETUP_DATA_CALL: + return "EVENT_SETUP_DATA_CALL"; + case EVENT_DEACTIVATE_DATA_CALL: + return "EVENT_DEACTIVATE_DATA_CALL"; + case EVENT_DATA_CALL_LIST_REQUEST: + return "EVENT_DATA_CALL_LIST_REQUEST"; + case EVENT_FORCE_CLOSE_TUNNEL: + return "EVENT_FORCE_CLOSE_TUNNEL"; + case EVENT_ADD_DATA_SERVICE_PROVIDER: + return "EVENT_ADD_DATA_SERVICE_PROVIDER"; + case EVENT_REMOVE_DATA_SERVICE_PROVIDER: + return "EVENT_REMOVE_DATA_SERVICE_PROVIDER"; + case IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT: + return "CARRIER_CONFIG_CHANGED_EVENT"; + case IwlanEventListener.CARRIER_CONFIG_UNKNOWN_CARRIER_EVENT: + return "CARRIER_CONFIG_UNKNOWN_CARRIER_EVENT"; + case IwlanEventListener.WIFI_CALLING_ENABLE_EVENT: + return "WIFI_CALLING_ENABLE_EVENT"; + case IwlanEventListener.WIFI_CALLING_DISABLE_EVENT: + return "WIFI_CALLING_DISABLE_EVENT"; + case IwlanEventListener.CROSS_SIM_CALLING_ENABLE_EVENT: + return "CROSS_SIM_CALLING_ENABLE_EVENT"; + case IwlanEventListener.CELLINFO_CHANGED_EVENT: + return "CELLINFO_CHANGED_EVENT"; + case EVENT_TUNNEL_OPENED_METRICS: + return "EVENT_TUNNEL_OPENED_METRICS"; + case EVENT_TUNNEL_CLOSED_METRICS: + return "EVENT_TUNNEL_CLOSED_METRICS"; + case IwlanEventListener.CALL_STATE_CHANGED_EVENT: + return "CALL_STATE_CHANGED_EVENT"; + default: + return "Unknown(" + event + ")"; + } + } + @Override public void onCreate() { - setAppContext(getApplicationContext()); + Context context = getApplicationContext().createAttributionContext(CONTEXT_ATTRIBUTION_TAG); + setAppContext(context); IwlanBroadcastReceiver.startListening(mContext); IwlanHelper.startCountryDetector(mContext); } @@ -1422,18 +2076,15 @@ public class IwlanDataService extends DataService { @Override public IBinder onBind(Intent intent) { - Log.d(TAG, "Iwlanservice onBind"); + Log.d(TAG, "IwlanDataService onBind"); return super.onBind(intent); } @Override public boolean onUnbind(Intent intent) { - Log.d(TAG, "IwlanService onUnbind"); - // force close all the tunnels when there are no clients - // active - for (IwlanDataServiceProvider dp : sIwlanDataServiceProviderList) { - dp.forceCloseTunnels(); - } + Log.d(TAG, "IwlanDataService onUnbind"); + getIwlanDataServiceHandler() + .sendMessage(getIwlanDataServiceHandler().obtainMessage(EVENT_FORCE_CLOSE_TUNNEL)); return super.onUnbind(intent); } @@ -1446,7 +2097,7 @@ public class IwlanDataService extends DataService { transport = "WIFI"; } pw.println("Default transport: " + transport); - for (IwlanDataServiceProvider provider : sIwlanDataServiceProviderList) { + for (IwlanDataServiceProvider provider : sIwlanDataServiceProviders.values()) { pw.println(); provider.dump(fd, pw, args); pw.println(); diff --git a/src/com/google/android/iwlan/IwlanError.java b/src/com/google/android/iwlan/IwlanError.java index f621a7a..fc03a01 100644 --- a/src/com/google/android/iwlan/IwlanError.java +++ b/src/com/google/android/iwlan/IwlanError.java @@ -16,16 +16,15 @@ package com.google.android.iwlan; -import android.net.ipsec.ike.exceptions.IkeException; import android.net.ipsec.ike.exceptions.IkeIOException; import android.net.ipsec.ike.exceptions.IkeInternalException; +import android.net.ipsec.ike.exceptions.IkeNetworkLostException; import android.net.ipsec.ike.exceptions.IkeProtocolException; import android.support.annotation.IntDef; import android.support.annotation.NonNull; import java.io.IOException; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; public class IwlanError { @@ -41,10 +40,14 @@ public class IwlanError { public static final int EPDG_SELECTOR_SERVER_SELECTION_FAILED = 4; public static final int TUNNEL_TRANSFORM_FAILED = 5; public static final int SIM_NOT_READY_EXCEPTION = 6; - public static final int NETWORK_FAILURE = 7; - - // Catch all exception - public static final int UNKNOWN_EXCEPTION = 8; // catch all + public static final int IKE_SESSION_CLOSED_BEFORE_CHILD_SESSION_OPENED = 7; + public static final int IKE_NETWORK_LOST_EXCEPTION = 8; + public static final int TUNNEL_NOT_FOUND = 9; + public static final int EPDG_ADDRESS_ONLY_IPV4_ALLOWED = 10; + public static final int EPDG_ADDRESS_ONLY_IPV6_ALLOWED = 11; + public static final int IKE_INIT_TIMEOUT = 12; + public static final int IKE_MOBILITY_TIMEOUT = 13; + public static final int IKE_DPD_TIMEOUT = 14; @IntDef({ NO_ERROR, @@ -54,28 +57,38 @@ public class IwlanError { EPDG_SELECTOR_SERVER_SELECTION_FAILED, TUNNEL_TRANSFORM_FAILED, SIM_NOT_READY_EXCEPTION, - NETWORK_FAILURE, - UNKNOWN_EXCEPTION + IKE_SESSION_CLOSED_BEFORE_CHILD_SESSION_OPENED, + IKE_NETWORK_LOST_EXCEPTION, + TUNNEL_NOT_FOUND, + EPDG_ADDRESS_ONLY_IPV4_ALLOWED, + EPDG_ADDRESS_ONLY_IPV6_ALLOWED, + IKE_INIT_TIMEOUT, + IKE_MOBILITY_TIMEOUT, + IKE_DPD_TIMEOUT }) - @interface IwlanErrorType {}; + @interface IwlanErrorType {} private static final Map<Integer, String> sErrorTypeStrings = - new ConcurrentHashMap<>() { - { - put(NO_ERROR, "IWLAN_NO_ERROR"); - put(IKE_PROTOCOL_EXCEPTION, "IWLAN_IKE_PROTOCOL_EXCEPTION"); - put(IKE_INTERNAL_IO_EXCEPTION, "IWLAN_IKE_INTERNAL_IO_EXCEPTION"); - put(IKE_GENERIC_EXCEPTION, "IWLAN_IKE_GENERIC_EXCEPTION"); - put( + Map.ofEntries( + Map.entry(NO_ERROR, "IWLAN_NO_ERROR"), + Map.entry(IKE_PROTOCOL_EXCEPTION, "IWLAN_IKE_PROTOCOL_EXCEPTION"), + Map.entry(IKE_INTERNAL_IO_EXCEPTION, "IWLAN_IKE_INTERNAL_IO_EXCEPTION"), + Map.entry(IKE_GENERIC_EXCEPTION, "IWLAN_IKE_GENERIC_EXCEPTION"), + Map.entry( EPDG_SELECTOR_SERVER_SELECTION_FAILED, - "IWLAN_EPDG_SELECTOR_SERVER_SELECTION_FAILED"); - put(TUNNEL_TRANSFORM_FAILED, "IWLAN_TUNNEL_TRANSFORM_FAILED"); - put(SIM_NOT_READY_EXCEPTION, "IWLAN_SIM_NOT_READY_EXCEPTION"); - put(NETWORK_FAILURE, "IWLAN_NETWORK_FAILURE"); - put(UNKNOWN_EXCEPTION, "IWLAN_UNKNOWN_EXCEPTION"); - } - }; - + "IWLAN_EPDG_SELECTOR_SERVER_SELECTION_FAILED"), + Map.entry(TUNNEL_TRANSFORM_FAILED, "IWLAN_TUNNEL_TRANSFORM_FAILED"), + Map.entry(SIM_NOT_READY_EXCEPTION, "IWLAN_SIM_NOT_READY_EXCEPTION"), + Map.entry( + IKE_SESSION_CLOSED_BEFORE_CHILD_SESSION_OPENED, + "IKE_SESSION_CLOSED_BEFORE_CHILD_SESSION_OPENED"), + Map.entry(IKE_NETWORK_LOST_EXCEPTION, "IWLAN_IKE_NETWORK_LOST_EXCEPTION"), + Map.entry(TUNNEL_NOT_FOUND, "IWLAN_TUNNEL_NOT_FOUND"), + Map.entry(IKE_INIT_TIMEOUT, "IKE_INIT_TIMEOUT"), + Map.entry(IKE_MOBILITY_TIMEOUT, "IKE_MOBILITY_TIMEOUT"), + Map.entry(IKE_DPD_TIMEOUT, "IKE_DPD_TIMEOUT"), + Map.entry(EPDG_ADDRESS_ONLY_IPV4_ALLOWED, "EPDG_ADDRESS_ONLY_IPV4_ALLOWED"), + Map.entry(EPDG_ADDRESS_ONLY_IPV6_ALLOWED, "EPDG_ADDRESS_ONLY_IPV6_ALLOWED")); private int mErrorType; private Exception mException; @@ -83,11 +96,16 @@ public class IwlanError { mErrorType = err; } + public IwlanError(@IwlanErrorType int err, @NonNull Exception exception) { + mErrorType = err; + mException = exception; + } + /** * Sets the IwlanError based on the Exception: 1. IkeException is base the class for all IKE * exception ErrorType: IKE_GENERIC_EXCEPTION. 2. IkeProtocolException is for specific protocol * errors (like IKE notify error codes) ErrorType: IKE_PROTOCOL_EXCEPTION 3. - * IkeInternalException is just a wrapper for various exeptions that IKE lib may encounter + * IkeInternalException is just a wrapper for various exceptions that IKE lib may encounter * ErrorType: IKE_INTERNAL_IO_EXCEPTION if the Exception is instance of IOException ErrorType: * IKE_GENERIC_EXCEPTION for all the other. */ @@ -99,11 +117,10 @@ public class IwlanError { IwlanErrorIkeIOException((IkeIOException) exception); } else if (exception instanceof IkeInternalException) { IwlanErrorIkeInternalException((IkeInternalException) exception); - } else if (exception instanceof IkeException) { - mErrorType = IKE_GENERIC_EXCEPTION; - mException = exception; + } else if (exception instanceof IkeNetworkLostException) { + IwlanErrorIkeNetworkLostException((IkeNetworkLostException) exception); } else { - mErrorType = UNKNOWN_EXCEPTION; + mErrorType = IKE_GENERIC_EXCEPTION; mException = exception; } } @@ -127,6 +144,11 @@ public class IwlanError { mException = exception; } + private void IwlanErrorIkeNetworkLostException(@NonNull IkeNetworkLostException exception) { + mErrorType = IKE_NETWORK_LOST_EXCEPTION; + mException = exception; + } + public @IwlanErrorType int getErrorType() { return mErrorType; } @@ -154,60 +176,31 @@ public class IwlanError { switch (mErrorType) { case IKE_GENERIC_EXCEPTION: - sb.append("MSG: " + mException.getMessage() + "\n CAUSE: "); + sb.append("MSG: ").append(mException.getMessage()).append("\n CAUSE: "); sb.append(mException.getCause()); break; - case UNKNOWN_EXCEPTION: - sb.append(mException.toString()); - break; case IKE_PROTOCOL_EXCEPTION: - sb.append("ERR: " + ((IkeProtocolException) mException).getErrorType() + "\nDATA:"); + sb.append("ERR: ") + .append(((IkeProtocolException) mException).getErrorType()) + .append("\nDATA:"); for (byte b : ((IkeProtocolException) mException).getErrorData()) { sb.append(String.format("%02x ", b)); } break; + case IKE_NETWORK_LOST_EXCEPTION: + sb.append("ERR: ") + .append(mException.getMessage()) + .append("\n CAUSE: ") + .append(mException.getCause()) + .append("\n NETWORK: ") + .append(((IkeNetworkLostException) mException).getNetwork()); + break; default: sb.append("-No Details-"); } return sb.toString(); } - /** - * Returns the error of the String. String that matches the name of the Error - * - * @param errorType string form of errorType - * @return IwlanErrorType - */ - public static int getErrorType(String errorType) { - int ret = IwlanError.UNKNOWN_EXCEPTION; - - // TODO: Add representation for Global error - switch (errorType) { - case "IKE_PROTOCOL_EXCEPTION": - ret = IwlanError.IKE_PROTOCOL_EXCEPTION; - break; - case "IKE_INTERNAL_IO_EXCEPTION": - ret = IwlanError.IKE_INTERNAL_IO_EXCEPTION; - break; - case "IKE_GENERIC_EXCEPTION": - ret = IwlanError.IKE_GENERIC_EXCEPTION; - break; - case "EPDG_SELECTOR_SERVER_SELECTION_FAILED": - ret = IwlanError.EPDG_SELECTOR_SERVER_SELECTION_FAILED; - break; - case "TUNNEL_TRANSFORM_FAILED": - ret = IwlanError.TUNNEL_TRANSFORM_FAILED; - break; - case "SIM_NOT_READY_EXCEPTION": - ret = IwlanError.SIM_NOT_READY_EXCEPTION; - break; - case "NETWORK_FAILURE": - ret = IwlanError.NETWORK_FAILURE; - break; - } - return ret; - } - @Override public boolean equals(Object o) { if (!(o instanceof IwlanError)) { @@ -231,4 +224,3 @@ public class IwlanError { return ret; } } -; diff --git a/src/com/google/android/iwlan/IwlanEventListener.java b/src/com/google/android/iwlan/IwlanEventListener.java index b4d38e2..f76c179 100644 --- a/src/com/google/android/iwlan/IwlanEventListener.java +++ b/src/com/google/android/iwlan/IwlanEventListener.java @@ -43,6 +43,7 @@ import com.android.internal.annotations.VisibleForTesting; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -58,7 +59,7 @@ public class IwlanEventListener { /** Airplane mode turned off or disabled. */ public static final int APM_DISABLE_EVENT = 3; - /** Airplame mode turned on or enabled */ + /** Airplane mode turned on or enabled */ public static final int APM_ENABLE_EVENT = 4; /** Wifi AccessPoint changed. */ @@ -85,6 +86,15 @@ public class IwlanEventListener { /** On Cellinfo changed */ public static final int CELLINFO_CHANGED_EVENT = 11; + /** On Call state changed */ + public static final int CALL_STATE_CHANGED_EVENT = 12; + + /* Events used and handled by IwlanDataService internally */ + public static final int DATA_SERVICE_INTERNAL_EVENT_BASE = 100; + + /* Events used and handled by IwlanNetworkService internally */ + public static final int NETWORK_SERVICE_INTERNAL_EVENT_BASE = 200; + @IntDef({ CARRIER_CONFIG_CHANGED_EVENT, WIFI_DISABLE_EVENT, @@ -96,27 +106,27 @@ public class IwlanEventListener { CROSS_SIM_CALLING_ENABLE_EVENT, CROSS_SIM_CALLING_DISABLE_EVENT, CARRIER_CONFIG_UNKNOWN_CARRIER_EVENT, - CELLINFO_CHANGED_EVENT + CELLINFO_CHANGED_EVENT, + CALL_STATE_CHANGED_EVENT }) - @interface IwlanEventType {}; + @interface IwlanEventType {} - private static String LOG_TAG = IwlanEventListener.class.getSimpleName(); + private static final String LOG_TAG = IwlanEventListener.class.getSimpleName(); private final String SUB_TAG; private static Boolean sIsAirplaneModeOn; - private static String sWifiSSID = new String(); + private static String sWifiSSID = ""; - private static Map<Integer, IwlanEventListener> mInstances = new ConcurrentHashMap<>(); + private static final Map<Integer, IwlanEventListener> mInstances = new ConcurrentHashMap<>(); - private Context mContext; - private int mSlotId; + private final Context mContext; + private final int mSlotId; private int mSubId; private Uri mCrossSimCallingUri; private Uri mWfcEnabledUri; private UserSettingContentObserver mUserSettingContentObserver; - private HandlerThread mUserSettingHandlerThread; private RadioInfoTelephonyCallback mTelephonyCallback; SparseArray<Set<Handler>> eventHandlers = new SparseArray<>(); @@ -128,6 +138,8 @@ public class IwlanEventListener { @Override public void onChange(boolean selfChange, Uri uri) { + Objects.requireNonNull(mCrossSimCallingUri, "CrossSimCallingUri must not be null"); + Objects.requireNonNull(mWfcEnabledUri, "WfcEnabledUri must not be null"); if (mCrossSimCallingUri.equals(uri)) { notifyCurrentSetting(uri); } else if (mWfcEnabledUri.equals(uri)) { @@ -137,26 +149,46 @@ public class IwlanEventListener { } private class RadioInfoTelephonyCallback extends TelephonyCallback - implements TelephonyCallback.CellInfoListener { + implements TelephonyCallback.CellInfoListener, TelephonyCallback.CallStateListener { @Override public void onCellInfoChanged(List<CellInfo> arrayCi) { Log.d(LOG_TAG, "Cellinfo changed"); - int event = CELLINFO_CHANGED_EVENT; for (Map.Entry<Integer, IwlanEventListener> entry : mInstances.entrySet()) { IwlanEventListener instance = entry.getValue(); if (instance != null) { - instance.updateHandlers(event, arrayCi); + instance.updateHandlers(arrayCi); + } + } + } + + @Override + public void onCallStateChanged(int state) { + Log.d( + LOG_TAG, + "Call state changed to " + callStateToString(state) + " for slot " + mSlotId); + + for (Map.Entry<Integer, IwlanEventListener> entry : mInstances.entrySet()) { + IwlanEventListener instance = entry.getValue(); + if (instance != null) { + instance.updateHandlers(CALL_STATE_CHANGED_EVENT, state); } } } } - /** Returns IwlanEventListener instance */ + /** + * Returns IwlanEventListener instance + */ public static IwlanEventListener getInstance(@NonNull Context context, int slotId) { return mInstances.computeIfAbsent(slotId, k -> new IwlanEventListener(context, slotId)); } + @VisibleForTesting + public static void resetAllInstances() { + mInstances.clear(); + } + /** * Adds handler for the list of events. * @@ -219,7 +251,7 @@ public class IwlanEventListener { * Report a Broadcast received. Mainly used by IwlanBroadcastReceiver to report the following * broadcasts CARRIER_CONFIG_CHANGED * - * @param Intent intent + * @param intent intent */ public static synchronized void onBroadcastReceived(Intent intent) { int event = UNKNOWN_EVENT; @@ -235,11 +267,11 @@ public class IwlanEventListener { TelephonyManager.UNKNOWN_CARRIER_ID); Context context = IwlanDataService.getContext(); if (slotId != SubscriptionManager.INVALID_SIM_SLOT_INDEX && context != null) { - getInstance(context, slotId).onCarrierConfigChanged(slotId, carrierId); + getInstance(context, slotId).onCarrierConfigChanged(carrierId); } break; case Intent.ACTION_AIRPLANE_MODE_CHANGED: - Boolean isAirplaneModeOn = new Boolean(intent.getBooleanExtra("state", false)); + Boolean isAirplaneModeOn = intent.getBooleanExtra("state", false); if (sIsAirplaneModeOn != null && sIsAirplaneModeOn.equals(isAirplaneModeOn)) { // no change in apm state break; @@ -269,7 +301,7 @@ public class IwlanEventListener { /** * Broadcast WIFI_AP_CHANGED_EVENT if Wifi SSID changed after Wifi connected. * - * @param Context context + * @param context context */ public static void onWifiConnected(Context context) { WifiManager wifiManager = context.getSystemService(WifiManager.class); @@ -292,11 +324,10 @@ public class IwlanEventListener { // Wifi. if (sWifiSSID.length() > 0 && !sWifiSSID.equals(wifiSSID)) { Log.d(LOG_TAG, "Wifi SSID changed"); - int event = WIFI_AP_CHANGED_EVENT; for (Map.Entry<Integer, IwlanEventListener> entry : mInstances.entrySet()) { IwlanEventListener instance = entry.getValue(); if (instance != null) { - instance.updateHandlers(event); + instance.updateHandlers(WIFI_AP_CHANGED_EVENT); } } } @@ -307,7 +338,6 @@ public class IwlanEventListener { * Returns the Event id of the String. String that matches the name of the event * * @param event String form of the event. - * @param int form of the event. */ public static int getUnthrottlingEvent(String event) { int ret = UNKNOWN_EVENT; @@ -357,7 +387,7 @@ public class IwlanEventListener { sIsAirplaneModeOn = null; } - private void onCarrierConfigChanged(int slotId, int carrierId) { + private void onCarrierConfigChanged(int carrierId) { Log.d(SUB_TAG, "onCarrierConfigChanged"); int subId = IwlanHelper.getSubId(mContext, mSlotId); if (subId != mSubId) { @@ -379,7 +409,7 @@ public class IwlanEventListener { } /** Unregister ContentObserver. */ - private void unregisterContentObserver() { + void unregisterContentObserver() { if (mUserSettingContentObserver != null) { mContext.getContentResolver().unregisterContentObserver(mUserSettingContentObserver); } @@ -390,10 +420,10 @@ public class IwlanEventListener { /** Initiate ContentObserver if it is not created. And, register it with the current sub id. */ private void registerContentObserver() { if (mUserSettingContentObserver == null) { - mUserSettingHandlerThread = + HandlerThread userSettingHandlerThread = new HandlerThread(IwlanNetworkService.class.getSimpleName()); - mUserSettingHandlerThread.start(); - Looper looper = mUserSettingHandlerThread.getLooper(); + userSettingHandlerThread.start(); + Looper looper = userSettingHandlerThread.getLooper(); Handler handler = new Handler(looper); mUserSettingContentObserver = new UserSettingContentObserver(handler); } @@ -448,7 +478,12 @@ public class IwlanEventListener { Log.e(SUB_TAG, "Could not find ImsMmTelManager"); return; } - boolean wfcEnabled = imsMmTelManager.isVoWiFiSettingEnabled(); + boolean wfcEnabled = false; + try { + wfcEnabled = imsMmTelManager.isVoWiFiSettingEnabled(); + } catch (IllegalArgumentException e) { + Log.w(SUB_TAG, e.getMessage()); + } int event = (wfcEnabled) ? WIFI_CALLING_ENABLE_EVENT : WIFI_CALLING_DISABLE_EVENT; getInstance(mContext, slotIndex).updateHandlers(event); } else { @@ -461,9 +496,10 @@ public class IwlanEventListener { Log.d(SUB_TAG, "registerTelephonyCallback"); TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class); telephonyManager = - telephonyManager.createForSubscriptionId(IwlanHelper.getSubId(mContext, mSlotId)); + Objects.requireNonNull(telephonyManager) + .createForSubscriptionId(IwlanHelper.getSubId(mContext, mSlotId)); mTelephonyCallback = new RadioInfoTelephonyCallback(); - telephonyManager.registerTelephonyCallback(r -> r.run(), mTelephonyCallback); + telephonyManager.registerTelephonyCallback(Runnable::run, mTelephonyCallback); } @VisibleForTesting @@ -485,17 +521,40 @@ public class IwlanEventListener { if (eventHandlers.contains(event)) { Log.d(SUB_TAG, "Updating handlers for the event: " + event); for (Handler handler : eventHandlers.get(event)) { - handler.obtainMessage(event).sendToTarget(); + handler.obtainMessage(event, mSlotId, 0 /* unused */).sendToTarget(); + } + } + } + + private synchronized void updateHandlers(List<CellInfo> arrayCi) { + int event = IwlanEventListener.CELLINFO_CHANGED_EVENT; + 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 */, arrayCi).sendToTarget(); } } } - private synchronized void updateHandlers(int event, List<CellInfo> arrayCi) { + private synchronized void updateHandlers(int event, int state) { if (eventHandlers.contains(event)) { Log.d(SUB_TAG, "Updating handlers for the event: " + event); for (Handler handler : eventHandlers.get(event)) { - handler.obtainMessage(event, arrayCi).sendToTarget(); + handler.obtainMessage(event, mSlotId, state).sendToTarget(); } } } + + private String callStateToString(int state) { + switch (state) { + case TelephonyManager.CALL_STATE_IDLE: + return "CALL_STATE_IDLE"; + case TelephonyManager.CALL_STATE_RINGING: + return "CALL_STATE_RINGING"; + case TelephonyManager.CALL_STATE_OFFHOOK: + return "CALL_STATE_OFFHOOK"; + default: + return "Unknown Call State (" + state + ")"; + } + } } diff --git a/src/com/google/android/iwlan/IwlanHelper.java b/src/com/google/android/iwlan/IwlanHelper.java index 7e39059..ade7189 100644 --- a/src/com/google/android/iwlan/IwlanHelper.java +++ b/src/com/google/android/iwlan/IwlanHelper.java @@ -27,6 +27,7 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.os.PersistableBundle; +import android.os.SystemClock; import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; @@ -109,14 +110,15 @@ public class IwlanHelper { return info; } - public static List<InetAddress> getAddressesForNetwork(Network network, Context context) { + // Retrieves all IP addresses for this Network, including stacked IPv4 link addresses. + public static List<InetAddress> getAllAddressesForNetwork(Network network, Context context) { ConnectivityManager connectivityManager = context.getSystemService(ConnectivityManager.class); List<InetAddress> gatewayList = new ArrayList<>(); if (network != null) { LinkProperties linkProperties = connectivityManager.getLinkProperties(network); if (linkProperties != null) { - for (LinkAddress linkAddr : linkProperties.getLinkAddresses()) { + for (LinkAddress linkAddr : linkProperties.getAllLinkAddresses()) { InetAddress inetAddr = linkAddr.getAddress(); // skip linklocal and loopback addresses if (!inetAddr.isLoopbackAddress() && !inetAddr.isLinkLocalAddress()) { @@ -131,25 +133,6 @@ public class IwlanHelper { return gatewayList; } - public static List<InetAddress> getStackedAddressesForNetwork( - Network network, Context context) { - ConnectivityManager connectivityManager = - context.getSystemService(ConnectivityManager.class); - List<InetAddress> gatewayList = new ArrayList<>(); - if (network != null) { - LinkProperties linkProperties = connectivityManager.getLinkProperties(network); - if (linkProperties != null) { - for (LinkAddress linkAddr : linkProperties.getAllLinkAddresses()) { - InetAddress inetAddr = linkAddr.getAddress(); - if ((inetAddr instanceof Inet4Address)) { - gatewayList.add(inetAddr); - } - } - } - } - return gatewayList; - } - /** * The method is to check if this IP address is an IPv4-embedded IPv6 address(Pref64::/n). * @@ -161,22 +144,24 @@ public class IwlanHelper { } public static boolean hasIpv6Address(List<InetAddress> localAddresses) { - for (InetAddress address : localAddresses) { - if (address instanceof Inet6Address) { - return true; + if (localAddresses != null) { + for (InetAddress address : localAddresses) { + if (address instanceof Inet6Address) { + return true; + } } } - return false; } public static boolean hasIpv4Address(List<InetAddress> localAddresses) { - for (InetAddress address : localAddresses) { - if (address instanceof Inet4Address) { - return true; + if (localAddresses != null) { + for (InetAddress address : localAddresses) { + if (address instanceof Inet4Address) { + return true; + } } } - return false; } @@ -313,4 +298,9 @@ public class IwlanHelper { } } } + + static long elapsedRealtime() { + /*Returns milliseconds since boot, including time spent in sleep.*/ + return SystemClock.elapsedRealtime(); + } } diff --git a/src/com/google/android/iwlan/IwlanNetworkService.java b/src/com/google/android/iwlan/IwlanNetworkService.java index 6adbaff..00f0907 100644 --- a/src/com/google/android/iwlan/IwlanNetworkService.java +++ b/src/com/google/android/iwlan/IwlanNetworkService.java @@ -25,11 +25,17 @@ import android.net.ConnectivityManager; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; +import android.net.NetworkSpecifier; +import android.net.TelephonyNetworkSpecifier; +import android.net.TransportInfo; +import android.net.vcn.VcnTransportInfo; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.support.annotation.NonNull; import android.telephony.AccessNetworkConstants; import android.telephony.NetworkRegistrationInfo; import android.telephony.NetworkService; @@ -41,28 +47,42 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; public class IwlanNetworkService extends NetworkService { private static final String TAG = IwlanNetworkService.class.getSimpleName(); - private Context mContext; + private static Context mContext; private IwlanNetworkMonitorCallback mNetworkMonitorCallback; private IwlanOnSubscriptionsChangedListener mSubsChangeListener; - private HandlerThread mNetworkCallbackHandlerThread; + private Handler mIwlanNetworkServiceHandler; + private HandlerThread mIwlanNetworkServiceHandlerThread; private static boolean sNetworkConnected; - private static List<IwlanNetworkServiceProvider> sIwlanNetworkServiceProviderList = - new ArrayList<IwlanNetworkServiceProvider>(); + private static final Map<Integer, IwlanNetworkServiceProvider> sIwlanNetworkServiceProviders = + new ConcurrentHashMap<>(); + private static final int INVALID_SUB_ID = -1; + + // The current subscription with the active internet PDN. Need not be the default data sub. + // If internet is over WiFi, this value will be INVALID_SUB_ID. + private static int mConnectedDataSub = INVALID_SUB_ID; + + private static final int EVENT_BASE = IwlanEventListener.NETWORK_SERVICE_INTERNAL_EVENT_BASE; + private static final int EVENT_NETWORK_REGISTRATION_INFO_REQUEST = EVENT_BASE; + private static final int EVENT_CREATE_NETWORK_SERVICE_PROVIDER = EVENT_BASE + 1; + private static final int EVENT_REMOVE_NETWORK_SERVICE_PROVIDER = EVENT_BASE + 2; @VisibleForTesting enum Transport { UNSPECIFIED_NETWORK, MOBILE, - WIFI; + WIFI } private static Transport sDefaultDataTransport = Transport.UNSPECIFIED_NETWORK; + // This callback runs in the same thread as IwlanNetworkServiceHandler final class IwlanNetworkMonitorCallback extends ConnectivityManager.NetworkCallback { /** Called when the framework connects and has declared a new network ready for use. */ @Override @@ -72,10 +92,11 @@ public class IwlanNetworkService extends NetworkService { /** * Called when the network is about to be lost, typically because there are no outstanding - * requests left for it. This may be paired with a {@link NetworkCallback#onAvailable} call - * with the new replacement network for graceful handover. This method is not guaranteed to - * be called before {@link NetworkCallback#onLost} is called, for example in case a network - * is suddenly disconnected. + * requests left for it. This may be paired with a {@link + * ConnectivityManager.NetworkCallback#onAvailable} call with the new replacement network + * for graceful handover. This method is not guaranteed to be called before {@link + * ConnectivityManager.NetworkCallback#onLost} is called, for example in case a network is + * suddenly disconnected. */ @Override public void onLosing(Network network, int maxMsToLive) { @@ -89,6 +110,7 @@ public class IwlanNetworkService extends NetworkService { @Override public void onLost(Network network) { Log.d(TAG, "onLost: " + network); + IwlanNetworkService.setConnectedDataSub(INVALID_SUB_ID); IwlanNetworkService.setNetworkConnected(false, Transport.UNSPECIFIED_NETWORK); } @@ -113,9 +135,12 @@ public class IwlanNetworkService extends NetworkService { Log.d(TAG, "onCapabilitiesChanged: " + network); if (networkCapabilities != null) { if (networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) { + IwlanNetworkService.setConnectedDataSub( + getConnectedDataSub(networkCapabilities)); IwlanNetworkService.setNetworkConnected( true, IwlanNetworkService.Transport.MOBILE); } else if (networkCapabilities.hasTransport(TRANSPORT_WIFI)) { + IwlanNetworkService.setConnectedDataSub(INVALID_SUB_ID); IwlanNetworkService.setNetworkConnected( true, IwlanNetworkService.Transport.WIFI); } else { @@ -128,12 +153,12 @@ public class IwlanNetworkService extends NetworkService { final class IwlanOnSubscriptionsChangedListener extends SubscriptionManager.OnSubscriptionsChangedListener { /** - * Callback invoked when there is any change to any SubscriptionInfo. Typically this method + * Callback invoked when there is any change to any SubscriptionInfo. Typically, this method * invokes {@link SubscriptionManager#getActiveSubscriptionInfoList} */ @Override public void onSubscriptionsChanged() { - for (IwlanNetworkServiceProvider np : sIwlanNetworkServiceProviderList) { + for (IwlanNetworkServiceProvider np : sIwlanNetworkServiceProviders.values()) { np.subscriptionChanged(); } } @@ -144,45 +169,6 @@ public class IwlanNetworkService extends NetworkService { private final IwlanNetworkService mIwlanNetworkService; private final String SUB_TAG; private boolean mIsSubActive = false; - private HandlerThread mHandlerThread; - private Handler mHandler; - - private final class NSPHandler extends Handler { - private final String TAG = - IwlanNetworkService.class.getSimpleName() - + NSPHandler.class.getSimpleName() - + "[" - + getSlotIndex() - + "]"; - - @Override - public void handleMessage(Message msg) { - Log.d(TAG, "msg.what = " + msg.what); - switch (msg.what) { - case IwlanEventListener.CROSS_SIM_CALLING_ENABLE_EVENT: - Log.d(TAG, "CROSS_SIM_CALLING_ENABLE_EVENT"); - notifyNetworkRegistrationInfoChanged(); - break; - case IwlanEventListener.CROSS_SIM_CALLING_DISABLE_EVENT: - Log.d(TAG, "CROSS_SIM_CALLING_DISABLE_EVENT"); - notifyNetworkRegistrationInfoChanged(); - break; - default: - Log.d(TAG, "Unknown message received!"); - break; - } - } - - NSPHandler(Looper looper) { - super(looper); - } - } - - Looper getLooper() { - mHandlerThread = new HandlerThread("NSPHandlerThread"); - mHandlerThread.start(); - return mHandlerThread.getLooper(); - } /** * Constructor @@ -195,53 +181,22 @@ public class IwlanNetworkService extends NetworkService { mIwlanNetworkService = iwlanNetworkService; // Register IwlanEventListener - initHandler(); List<Integer> events = new ArrayList<Integer>(); events.add(IwlanEventListener.CROSS_SIM_CALLING_ENABLE_EVENT); events.add(IwlanEventListener.CROSS_SIM_CALLING_DISABLE_EVENT); - IwlanEventListener.getInstance(mContext, slotIndex).addEventListener(events, mHandler); - } - - void initHandler() { - mHandler = new NSPHandler(getLooper()); + IwlanEventListener.getInstance(mContext, slotIndex) + .addEventListener(events, getIwlanNetworkServiceHandler()); } @Override public void requestNetworkRegistrationInfo(int domain, NetworkServiceCallback callback) { - if (callback == null) { - Log.d(SUB_TAG, "Error: callback is null. returning"); - return; - } - if (domain != NetworkRegistrationInfo.DOMAIN_PS) { - callback.onRequestNetworkRegistrationInfoComplete( - NetworkServiceCallback.RESULT_ERROR_UNSUPPORTED, null); - return; - } - - NetworkRegistrationInfo.Builder nriBuilder = new NetworkRegistrationInfo.Builder(); - nriBuilder - .setAvailableServices(Arrays.asList(NetworkRegistrationInfo.SERVICE_TYPE_DATA)) - .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WLAN) - .setEmergencyOnly(!mIsSubActive) - .setDomain(NetworkRegistrationInfo.DOMAIN_PS); - - if (!IwlanNetworkService.isNetworkConnected( - IwlanHelper.isDefaultDataSlot(mContext, getSlotIndex()), - IwlanHelper.isCrossSimCallingEnabled(mContext, getSlotIndex()))) { - nriBuilder - .setRegistrationState( - NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_SEARCHING) - .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_UNKNOWN); - Log.d(SUB_TAG, "reg state REGISTRATION_STATE_NOT_REGISTERED_SEARCHING"); - } else { - nriBuilder - .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME) - .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_IWLAN); - Log.d(SUB_TAG, "reg state REGISTRATION_STATE_HOME"); - } - - callback.onRequestNetworkRegistrationInfoComplete( - NetworkServiceCallback.RESULT_SUCCESS, nriBuilder.build()); + getIwlanNetworkServiceHandler() + .sendMessage( + getIwlanNetworkServiceHandler() + .obtainMessage( + EVENT_NETWORK_REGISTRATION_INFO_REQUEST, + new NetworkRegistrationInfoRequestData( + domain, callback, this))); } /** @@ -252,17 +207,16 @@ public class IwlanNetworkService extends NetworkService { @Override public void close() { mIwlanNetworkService.removeNetworkServiceProvider(this); - IwlanEventListener.getInstance(mContext, getSlotIndex()).removeEventListener(mHandler); - mHandlerThread.quit(); + IwlanEventListener.getInstance(mContext, getSlotIndex()) + .removeEventListener(getIwlanNetworkServiceHandler()); } @VisibleForTesting void subscriptionChanged() { - boolean subActive = false; - SubscriptionManager sm = SubscriptionManager.from(mContext); - if (sm.getActiveSubscriptionInfoForSimSlotIndex(getSlotIndex()) != null) { - subActive = true; - } + boolean subActive = + getSubscriptionManager() + .getActiveSubscriptionInfoForSimSlotIndex(getSlotIndex()) + != null; if (subActive == mIsSubActive) { return; } @@ -277,6 +231,122 @@ public class IwlanNetworkService extends NetworkService { } } + private final class IwlanNetworkServiceHandler extends Handler { + private final String TAG = IwlanNetworkServiceHandler.class.getSimpleName(); + + @Override + public void handleMessage(Message msg) { + Log.d(TAG, "msg.what = " + eventToString(msg.what)); + + IwlanNetworkServiceProvider iwlanNetworkServiceProvider; + int slotId; + + switch (msg.what) { + case IwlanEventListener.CROSS_SIM_CALLING_ENABLE_EVENT: + case IwlanEventListener.CROSS_SIM_CALLING_DISABLE_EVENT: + iwlanNetworkServiceProvider = getNetworkServiceProvider(msg.arg1); + iwlanNetworkServiceProvider.notifyNetworkRegistrationInfoChanged(); + break; + + case EVENT_NETWORK_REGISTRATION_INFO_REQUEST: + NetworkRegistrationInfoRequestData networkRegistrationInfoRequestData = + (NetworkRegistrationInfoRequestData) msg.obj; + int domain = networkRegistrationInfoRequestData.mDomain; + NetworkServiceCallback callback = networkRegistrationInfoRequestData.mCallback; + iwlanNetworkServiceProvider = + networkRegistrationInfoRequestData.mIwlanNetworkServiceProvider; + + if (callback == null) { + Log.d(TAG, "Error: callback is null. returning"); + return; + } + if (domain != NetworkRegistrationInfo.DOMAIN_PS) { + callback.onRequestNetworkRegistrationInfoComplete( + NetworkServiceCallback.RESULT_ERROR_UNSUPPORTED, null); + return; + } + + NetworkRegistrationInfo.Builder nriBuilder = + new NetworkRegistrationInfo.Builder(); + nriBuilder + .setAvailableServices( + List.of(NetworkRegistrationInfo.SERVICE_TYPE_DATA)) + .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WLAN) + .setEmergencyOnly(!iwlanNetworkServiceProvider.mIsSubActive) + .setDomain(NetworkRegistrationInfo.DOMAIN_PS); + + slotId = iwlanNetworkServiceProvider.getSlotIndex(); + if (!IwlanNetworkService.isNetworkConnected( + isActiveDataOnOtherSub(slotId), + IwlanHelper.isCrossSimCallingEnabled(mContext, slotId))) { + nriBuilder + .setRegistrationState( + NetworkRegistrationInfo + .REGISTRATION_STATE_NOT_REGISTERED_SEARCHING) + .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_UNKNOWN); + Log.d( + TAG + "[" + slotId + "]", + ": reg state" + " REGISTRATION_STATE_NOT_REGISTERED_SEARCHING"); + } else { + nriBuilder + .setRegistrationState( + NetworkRegistrationInfo.REGISTRATION_STATE_HOME) + .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_IWLAN); + Log.d(TAG + "[" + slotId + "]", ": reg state REGISTRATION_STATE_HOME"); + } + + callback.onRequestNetworkRegistrationInfoComplete( + NetworkServiceCallback.RESULT_SUCCESS, nriBuilder.build()); + break; + + case EVENT_CREATE_NETWORK_SERVICE_PROVIDER: + iwlanNetworkServiceProvider = (IwlanNetworkServiceProvider) msg.obj; + + if (sIwlanNetworkServiceProviders.isEmpty()) { + initCallback(); + } + + addIwlanNetworkServiceProvider(iwlanNetworkServiceProvider); + break; + + case EVENT_REMOVE_NETWORK_SERVICE_PROVIDER: + iwlanNetworkServiceProvider = (IwlanNetworkServiceProvider) msg.obj; + slotId = iwlanNetworkServiceProvider.getSlotIndex(); + IwlanNetworkServiceProvider nsp = sIwlanNetworkServiceProviders.remove(slotId); + if (nsp == null) { + Log.w( + TAG + "[" + slotId + "]", + "No NetworkServiceProvider exists for slot!"); + return; + } + if (sIwlanNetworkServiceProviders.isEmpty()) { + deinitCallback(); + } + break; + + default: + throw new IllegalStateException("Unexpected value: " + msg.what); + } + } + + IwlanNetworkServiceHandler(Looper looper) { + super(looper); + } + } + + private static final class NetworkRegistrationInfoRequestData { + final int mDomain; + final NetworkServiceCallback mCallback; + final IwlanNetworkServiceProvider mIwlanNetworkServiceProvider; + + private NetworkRegistrationInfoRequestData( + int domain, NetworkServiceCallback callback, IwlanNetworkServiceProvider nsp) { + mDomain = domain; + mCallback = callback; + mIwlanNetworkServiceProvider = nsp; + } + } + /** * Create the instance of {@link NetworkServiceProvider}. Network service provider must override * this method to facilitate the creation of {@link NetworkServiceProvider} instances. The @@ -292,37 +362,43 @@ public class IwlanNetworkService extends NetworkService { // TODO: validity check slot index - if (sIwlanNetworkServiceProviderList.isEmpty()) { - // first invocation - mNetworkCallbackHandlerThread = - new HandlerThread(IwlanNetworkService.class.getSimpleName()); - mNetworkCallbackHandlerThread.start(); - Looper looper = mNetworkCallbackHandlerThread.getLooper(); - Handler handler = new Handler(looper); - - // register for default network callback - ConnectivityManager connectivityManager = - mContext.getSystemService(ConnectivityManager.class); - mNetworkMonitorCallback = new IwlanNetworkMonitorCallback(); - connectivityManager.registerDefaultNetworkCallback(mNetworkMonitorCallback, handler); - Log.d(TAG, "Registered with Connectivity Service"); - - /* register with subscription manager */ - SubscriptionManager subscriptionManager = - mContext.getSystemService(SubscriptionManager.class); - mSubsChangeListener = new IwlanOnSubscriptionsChangedListener(); - subscriptionManager.addOnSubscriptionsChangedListener(mSubsChangeListener); - Log.d(TAG, "Registered with Subscription Service"); - } - IwlanNetworkServiceProvider np = new IwlanNetworkServiceProvider(slotIndex, this); - sIwlanNetworkServiceProviderList.add(np); + getIwlanNetworkServiceHandler() + .sendMessage( + getIwlanNetworkServiceHandler() + .obtainMessage(EVENT_CREATE_NETWORK_SERVICE_PROVIDER, np)); return np; } - public static boolean isNetworkConnected(boolean isDds, boolean isCstEnabled) { - if (!isDds && isCstEnabled) { - // Only Non-DDS sub with CST enabled, can use any transport. + static void setConnectedDataSub(int subId) { + mConnectedDataSub = subId; + } + + static int getConnectedDataSub(NetworkCapabilities networkCapabilities) { + int connectedDataSub = INVALID_SUB_ID; + NetworkSpecifier specifier = networkCapabilities.getNetworkSpecifier(); + TransportInfo transportInfo = networkCapabilities.getTransportInfo(); + + if (specifier instanceof TelephonyNetworkSpecifier) { + connectedDataSub = ((TelephonyNetworkSpecifier) specifier).getSubscriptionId(); + } else if (transportInfo instanceof VcnTransportInfo) { + connectedDataSub = ((VcnTransportInfo) transportInfo).getSubId(); + } + return connectedDataSub; + } + + static boolean isActiveDataOnOtherSub(int slotId) { + int subId = IwlanHelper.getSubId(mContext, slotId); + return mConnectedDataSub != INVALID_SUB_ID && subId != mConnectedDataSub; + } + + public static boolean isNetworkConnected(boolean isActiveDataOnOtherSub, boolean isCstEnabled) { + if (isActiveDataOnOtherSub && isCstEnabled) { + // For cross-SIM IWLAN (Transport.MOBILE), an active data PDN must be maintained on the + // other subscription. + if (sNetworkConnected && (sDefaultDataTransport != Transport.MOBILE)) { + Log.e(TAG, "Internet is on other slot, but default transport is not MOBILE!"); + } return sNetworkConnected; } else { // For all other cases, only wifi transport can be used. @@ -340,32 +416,56 @@ public class IwlanNetworkService extends NetworkService { sNetworkConnected = connected; sDefaultDataTransport = transport; - for (IwlanNetworkServiceProvider np : sIwlanNetworkServiceProviderList) { + for (IwlanNetworkServiceProvider np : sIwlanNetworkServiceProviders.values()) { np.notifyNetworkRegistrationInfoChanged(); } } - public void removeNetworkServiceProvider(IwlanNetworkServiceProvider np) { - sIwlanNetworkServiceProviderList.remove(np); - if (sIwlanNetworkServiceProviderList.isEmpty()) { - // deinit network related stuff - ConnectivityManager connectivityManager = - mContext.getSystemService(ConnectivityManager.class); - connectivityManager.unregisterNetworkCallback(mNetworkMonitorCallback); - mNetworkCallbackHandlerThread.quit(); // no need to quitSafely - mNetworkCallbackHandlerThread = null; - mNetworkMonitorCallback = null; - - // deinit subscription manager related stuff - SubscriptionManager subscriptionManager = - mContext.getSystemService(SubscriptionManager.class); - subscriptionManager.removeOnSubscriptionsChangedListener(mSubsChangeListener); - mSubsChangeListener = null; + void addIwlanNetworkServiceProvider(IwlanNetworkServiceProvider np) { + int slotIndex = np.getSlotIndex(); + if (sIwlanNetworkServiceProviders.containsKey(slotIndex)) { + throw new IllegalStateException( + "NetworkServiceProvider already exists for slot " + slotIndex); } + sIwlanNetworkServiceProviders.put(slotIndex, np); + } + + public void removeNetworkServiceProvider(IwlanNetworkServiceProvider np) { + getIwlanNetworkServiceHandler() + .sendMessage( + getIwlanNetworkServiceHandler() + .obtainMessage(EVENT_REMOVE_NETWORK_SERVICE_PROVIDER, np)); } - Context getContext() { - return getApplicationContext(); + void initCallback() { + // register for default network callback + mNetworkMonitorCallback = new IwlanNetworkMonitorCallback(); + getConnectivityManager() + .registerSystemDefaultNetworkCallback( + mNetworkMonitorCallback, getIwlanNetworkServiceHandler()); + Log.d(TAG, "Registered with Connectivity Service"); + + /* register with subscription manager */ + mSubsChangeListener = new IwlanOnSubscriptionsChangedListener(); + getSubscriptionManager() + .addOnSubscriptionsChangedListener( + new HandlerExecutor(getIwlanNetworkServiceHandler()), mSubsChangeListener); + Log.d(TAG, "Registered with Subscription Service"); + } + + void deinitCallback() { + // deinit network related stuff + getConnectivityManager().unregisterNetworkCallback(mNetworkMonitorCallback); + mNetworkMonitorCallback = null; + + // deinit subscription manager related stuff + getSubscriptionManager().removeOnSubscriptionsChangedListener(mSubsChangeListener); + mSubsChangeListener = null; + if (mIwlanNetworkServiceHandlerThread != null) { + mIwlanNetworkServiceHandlerThread.quit(); + mIwlanNetworkServiceHandlerThread = null; + } + mIwlanNetworkServiceHandler = null; } @VisibleForTesting @@ -375,12 +475,45 @@ public class IwlanNetworkService extends NetworkService { @VisibleForTesting IwlanNetworkServiceProvider getNetworkServiceProvider(int slotIndex) { - for (IwlanNetworkServiceProvider np : sIwlanNetworkServiceProviderList) { - if (np.getSlotIndex() == slotIndex) { - return np; - } + return sIwlanNetworkServiceProviders.get(slotIndex); + } + + @VisibleForTesting + IwlanNetworkMonitorCallback getNetworkMonitorCallback() { + return mNetworkMonitorCallback; + } + + @VisibleForTesting + @NonNull + Handler getIwlanNetworkServiceHandler() { + if (mIwlanNetworkServiceHandler == null) { + mIwlanNetworkServiceHandler = new IwlanNetworkServiceHandler(getLooper()); + } + return mIwlanNetworkServiceHandler; + } + + @VisibleForTesting + Looper getLooper() { + mIwlanNetworkServiceHandlerThread = new HandlerThread("IwlanNetworkServiceThread"); + mIwlanNetworkServiceHandlerThread.start(); + return mIwlanNetworkServiceHandlerThread.getLooper(); + } + + private static String eventToString(int event) { + switch (event) { + case IwlanEventListener.CROSS_SIM_CALLING_ENABLE_EVENT: + return "CROSS_SIM_CALLING_ENABLE_EVENT"; + case IwlanEventListener.CROSS_SIM_CALLING_DISABLE_EVENT: + return "CROSS_SIM_CALLING_DISABLE_EVENT"; + case EVENT_NETWORK_REGISTRATION_INFO_REQUEST: + return "EVENT_NETWORK_REGISTRATION_INFO_REQUEST"; + case EVENT_CREATE_NETWORK_SERVICE_PROVIDER: + return "EVENT_CREATE_NETWORK_SERVICE_PROVIDER"; + case EVENT_REMOVE_NETWORK_SERVICE_PROVIDER: + return "EVENT_REMOVE_NETWORK_SERVICE_PROVIDER"; + default: + return "Unknown(" + event + ")"; } - return null; } @Override @@ -393,4 +526,14 @@ public class IwlanNetworkService extends NetworkService { Log.d(TAG, "IwlanNetworkService onBind"); return super.onBind(intent); } + + @NonNull + ConnectivityManager getConnectivityManager() { + return Objects.requireNonNull(mContext.getSystemService(ConnectivityManager.class)); + } + + @NonNull + SubscriptionManager getSubscriptionManager() { + return Objects.requireNonNull(mContext.getSystemService(SubscriptionManager.class)); + } } diff --git a/src/com/google/android/iwlan/IwlanTunnelMetricsImpl.java b/src/com/google/android/iwlan/IwlanTunnelMetricsImpl.java new file mode 100644 index 0000000..3c04de5 --- /dev/null +++ b/src/com/google/android/iwlan/IwlanTunnelMetricsImpl.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2022 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.google.android.iwlan; + +import android.os.Handler; +import com.google.android.iwlan.IwlanDataService.IwlanDataServiceProvider; + +public class IwlanTunnelMetricsImpl implements TunnelMetricsInterface { + IwlanDataServiceProvider mDataServiceProvider; + Handler mIwlanDataServiceHandler; + + private static final int EVENT_BASE = IwlanEventListener.DATA_SERVICE_INTERNAL_EVENT_BASE; + private static final int EVENT_TUNNEL_OPENED_METRICS = EVENT_BASE + 8; + private static final int EVENT_TUNNEL_CLOSED_METRICS = EVENT_BASE + 9; + + public IwlanTunnelMetricsImpl(IwlanDataServiceProvider dsp, Handler handler) { + mDataServiceProvider = dsp; + mIwlanDataServiceHandler = handler; + } + + public void onOpened(OnOpenedMetrics metricsData) { + metricsData.setIwlanDataServiceProvider(mDataServiceProvider); + mIwlanDataServiceHandler.sendMessage( + mIwlanDataServiceHandler.obtainMessage(EVENT_TUNNEL_OPENED_METRICS, metricsData)); + } + + public void onClosed(OnClosedMetrics metricsData) { + metricsData.setIwlanDataServiceProvider(mDataServiceProvider); + mIwlanDataServiceHandler.sendMessage( + mIwlanDataServiceHandler.obtainMessage(EVENT_TUNNEL_CLOSED_METRICS, metricsData)); + } +} diff --git a/src/com/google/android/iwlan/TunnelMetricsInterface.java b/src/com/google/android/iwlan/TunnelMetricsInterface.java new file mode 100644 index 0000000..6e79688 --- /dev/null +++ b/src/com/google/android/iwlan/TunnelMetricsInterface.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2022 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.google.android.iwlan; + +import java.net.InetAddress; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.google.android.iwlan.IwlanDataService.IwlanDataServiceProvider; + +import java.util.Objects; + +public interface TunnelMetricsInterface { + /** Called for logging the tunnel is opened. */ + void onOpened(OnOpenedMetrics metricsData); + /** Called for logging the tunnel is closed or bring up failed. */ + void onClosed(OnClosedMetrics metricsData); + + static class TunnelMetricsData { + private final String mApnName; + private final String mEpdgServerAddress; + private final int mEpdgServerSelectionDuration; + private final int mIkeTunnelEstablishmentDuration; + private IwlanDataServiceProvider mIwlanDataServiceProvider; + + protected TunnelMetricsData(Builder builder) { + this.mApnName = builder.mApnName; + this.mEpdgServerAddress = builder.mEpdgServerAddress; + this.mEpdgServerSelectionDuration = builder.mEpdgServerSelectionDuration; + this.mIkeTunnelEstablishmentDuration = builder.mIkeTunnelEstablishmentDuration; + } + + @Nullable + public String getApnName() { + return mApnName; + } + + @Nullable + public String getEpdgServerAddress() { + return mEpdgServerAddress; + } + + public int getEpdgServerSelectionDuration() { + return mEpdgServerSelectionDuration; + } + + public int getIkeTunnelEstablishmentDuration() { + return mIkeTunnelEstablishmentDuration; + } + + public IwlanDataServiceProvider getIwlanDataServiceProvider() { + return mIwlanDataServiceProvider; + } + + public void setIwlanDataServiceProvider(IwlanDataServiceProvider dsp) { + mIwlanDataServiceProvider = dsp; + } + + public static class Builder<T extends Builder> { + @Nullable private String mApnName = null; + @Nullable private String mEpdgServerAddress = null; + private int mEpdgServerSelectionDuration = 0; + private int mIkeTunnelEstablishmentDuration = 0; + + /** Default constructor for Builder. */ + public Builder() {} + + public T setApnName(@NonNull String apnName) { + mApnName = Objects.requireNonNull(apnName, "apnName must not be null"); + return (T) this; + } + + public T setEpdgServerAddress(InetAddress epdgAddress) { + mEpdgServerAddress = epdgAddress == null ? null : epdgAddress.getHostAddress(); + return (T) this; + } + + public T setEpdgServerSelectionDuration(int epdgServerSelectionDuration) { + mEpdgServerSelectionDuration = epdgServerSelectionDuration; + return (T) this; + } + + public T setIkeTunnelEstablishmentDuration(int ikeTunnelEstablishmentDuration) { + mIkeTunnelEstablishmentDuration = ikeTunnelEstablishmentDuration; + return (T) this; + } + + public TunnelMetricsData build() { + if (mApnName == null) { + throw new IllegalArgumentException("Necessary parameter missing."); + } + return new TunnelMetricsData(this); + } + } + } + + static class OnOpenedMetrics extends TunnelMetricsData { + + protected OnOpenedMetrics(Builder builder) { + super(builder); + } + + public static class Builder extends TunnelMetricsData.Builder<Builder> { + + public Builder() {} + + public OnOpenedMetrics build() { + return new OnOpenedMetrics(this); + } + } + } + + static class OnClosedMetrics extends TunnelMetricsData { + + protected OnClosedMetrics(Builder builder) { + super(builder); + } + + public static class Builder extends TunnelMetricsData.Builder<Builder> { + + public Builder() {} + + public OnClosedMetrics build() { + return new OnClosedMetrics(this); + } + } + } +} diff --git a/src/com/google/android/iwlan/epdg/EpdgSelector.java b/src/com/google/android/iwlan/epdg/EpdgSelector.java index 54b082f..4534b81 100644 --- a/src/com/google/android/iwlan/epdg/EpdgSelector.java +++ b/src/com/google/android/iwlan/epdg/EpdgSelector.java @@ -19,6 +19,7 @@ package com.google.android.iwlan.epdg; import android.content.Context; import android.net.DnsResolver; import android.net.DnsResolver.DnsException; +import android.net.InetAddresses; import android.net.Network; import android.support.annotation.IntDef; import android.support.annotation.NonNull; @@ -34,6 +35,7 @@ import android.telephony.CellInfoLte; import android.telephony.CellInfoNr; import android.telephony.CellInfoTdscdma; import android.telephony.CellInfoWcdma; +import android.telephony.DataFailCause; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; @@ -42,6 +44,7 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.google.android.iwlan.ErrorPolicyManager; import com.google.android.iwlan.IwlanError; import com.google.android.iwlan.IwlanHelper; import com.google.android.iwlan.epdg.NaptrDnsResolver.NaptrTarget; @@ -51,32 +54,79 @@ import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.*; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; public class EpdgSelector { private static final String TAG = "EpdgSelector"; - private Context mContext; - private int mSlotId; - private static ConcurrentHashMap<Integer, EpdgSelector> mSelectorInstances = + private final Context mContext; + private final int mSlotId; + private static final ConcurrentHashMap<Integer, EpdgSelector> mSelectorInstances = new ConcurrentHashMap<>(); private int mV4PcoId = -1; private int mV6PcoId = -1; private byte[] mV4PcoData = null; private byte[] mV6PcoData = null; + @NonNull private final ErrorPolicyManager mErrorPolicyManager; + + // 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; + + private static final long PARALLEL_STATIC_RESOLUTION_TIMEOUT_DURATION_SEC = 6L; + private static final long PARALLEL_PLMN_RESOLUTION_TIMEOUT_DURATION_SEC = 20L; + private static final int NUM_EPDG_SELECTION_EXECUTORS = 2; // 1 each for normal selection, SOS. + private static final int MAX_EPDG_SELECTION_THREADS = 2; // 1 each for prefetch, tunnel bringup. + private static final int MAX_DNS_RESOLVER_THREADS = 25; // Do not expect > 25 FQDNs per carrier. + private static final String NO_DOMAIN = "NO_DOMAIN"; + + BlockingQueue<Runnable> dnsResolutionQueue = + new ArrayBlockingQueue<>( + MAX_DNS_RESOLVER_THREADS + * MAX_EPDG_SELECTION_THREADS + * NUM_EPDG_SELECTION_EXECUTORS); + + Executor mDnsResolutionExecutor = + new ThreadPoolExecutor( + 0, MAX_DNS_RESOLVER_THREADS, 60L, TimeUnit.SECONDS, dnsResolutionQueue); + + ExecutorService mEpdgSelectionExecutor = + new ThreadPoolExecutor( + 0, + MAX_EPDG_SELECTION_THREADS, + 60L, + TimeUnit.SECONDS, + new SynchronousQueue<Runnable>()); + Future mDnsPrefetchFuture; + + ExecutorService mSosEpdgSelectionExecutor = + new ThreadPoolExecutor( + 0, + MAX_EPDG_SELECTION_THREADS, + 60L, + TimeUnit.SECONDS, + new SynchronousQueue<Runnable>()); + Future mSosDnsPrefetchFuture; final Comparator<InetAddress> inetAddressComparator = - new Comparator<InetAddress>() { - @Override - public int compare(InetAddress ip1, InetAddress ip2) { - if ((ip1 instanceof Inet4Address) && (ip2 instanceof Inet6Address)) { - return -1; - } else if ((ip1 instanceof Inet6Address) && (ip2 instanceof Inet4Address)) { - return 1; - } else { - return 0; - } + (ip1, ip2) -> { + if ((ip1 instanceof Inet4Address) && (ip2 instanceof Inet6Address)) { + return -1; + } else if ((ip1 instanceof Inet6Address) && (ip2 instanceof Inet4Address)) { + return 1; + } else { + return 0; } }; @@ -87,9 +137,16 @@ public class EpdgSelector { @IntDef({PROTO_FILTER_IPV4, PROTO_FILTER_IPV6, PROTO_FILTER_IPV4V6}) @interface ProtoFilter {} + public static final int IPV4_PREFERRED = 0; + public static final int IPV6_PREFERRED = 1; + public static final int SYSTEM_PREFERRED = 2; + + @IntDef({IPV4_PREFERRED, IPV6_PREFERRED, SYSTEM_PREFERRED}) + @interface EpdgAddressOrder {} + public interface EpdgSelectorCallback { /*gives priority ordered list of addresses*/ - void onServerListChanged(int transactionId, ArrayList<InetAddress> validIPList); + void onServerListChanged(int transactionId, List<InetAddress> validIPList); void onError(int transactionId, IwlanError error); } @@ -98,6 +155,8 @@ public class EpdgSelector { EpdgSelector(Context context, int slotId) { mContext = context; mSlotId = slotId; + + mErrorPolicyManager = ErrorPolicyManager.getInstance(mContext, mSlotId); } public static EpdgSelector getSelectorInstance(Context context, int slotId) { @@ -106,7 +165,12 @@ public class EpdgSelector { } public boolean setPcoData(int pcoId, byte[] pcoData) { - Log.d(TAG, "onReceive PcoId:" + String.format("0x%04x", pcoId) + " PcoData:" + pcoData); + Log.d( + TAG, + "onReceive PcoId:" + + String.format("0x%04x", pcoId) + + " PcoData:" + + Arrays.toString(pcoData)); int PCO_ID_IPV6 = IwlanHelper.getConfig( @@ -143,69 +207,242 @@ public class EpdgSelector { mV6PcoData = null; } + 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(); + + final DnsResolver.Callback<List<InetAddress>> cb = + new DnsResolver.Callback<List<InetAddress>>() { + @Override + public void onAnswer(@NonNull final List<InetAddress> answer, final int rcode) { + if (rcode != 0) { + Log.e( + TAG, + "DnsResolver Response Code = " + + rcode + + " for domain " + + domainName); + } + Map.Entry<String, List<InetAddress>> entry = Map.entry(domainName, answer); + result.complete(entry); + } + + @Override + public void onError(@Nullable final DnsResolver.DnsException error) { + Log.e( + TAG, + "Resolve DNS with error: " + error + " for domain: " + domainName); + result.complete(null); + } + }; + DnsResolver.getInstance() + .query(network, domainName, queryType, DnsResolver.FLAG_EMPTY, executor, null, cb); + return result; + } + + private List<InetAddress> v4v6ProtocolFilter(List<InetAddress> ipList, int filter) { + List<InetAddress> validIpList = new ArrayList<>(); + for (InetAddress ipAddress : ipList) { + if (IwlanHelper.isIpv4EmbeddedIpv6Address(ipAddress)) { + continue; + } + switch (filter) { + case PROTO_FILTER_IPV4: + if (ipAddress instanceof Inet4Address) { + validIpList.add(ipAddress); + } + break; + case PROTO_FILTER_IPV6: + if (ipAddress instanceof Inet6Address) { + validIpList.add(ipAddress); + } + break; + case PROTO_FILTER_IPV4V6: + validIpList.add(ipAddress); + break; + default: + Log.d(TAG, "Invalid ProtoFilter : " + filter); + } + } + return validIpList; + } + + // Converts a list of CompletableFutures of type T into a single CompletableFuture containing a + // list of T. The resulting CompletableFuture waits for all futures to complete, + // even if any future throw an exception. + private <T> CompletableFuture<List<T>> allOf(List<CompletableFuture<T>> futuresList) { + CompletableFuture<Void> allFuturesResult = + CompletableFuture.allOf( + futuresList.toArray(new CompletableFuture[futuresList.size()])); + return allFuturesResult.thenApply( + v -> + futuresList.stream() + .map(CompletableFuture::join) + .filter(Objects::nonNull) + .collect(Collectors.<T>toList())); + } + + @VisibleForTesting + protected boolean hasIpv4Address(Network network) { + return IwlanHelper.hasIpv4Address(IwlanHelper.getAllAddressesForNetwork(network, mContext)); + } + + @VisibleForTesting + protected boolean hasIpv6Address(Network network) { + return IwlanHelper.hasIpv6Address(IwlanHelper.getAllAddressesForNetwork(network, mContext)); + } + + private void printParallelDnsResult(Map<String, List<InetAddress>> domainNameToIpAddresses) { + Log.d(TAG, "Parallel DNS resolution result:"); + for (String domain : domainNameToIpAddresses.keySet()) { + Log.d(TAG, domain + ": " + domainNameToIpAddresses.get(domain)); + } + } + /** + * 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. + * + * @param domainNames Domain names for which DNS resolution needs to be performed. + * @param filter Selects for IPv4, IPv6 (or both) addresses from the resulting DNS records + * @param network {@link Network} Network on which to run the DNS query. + * @param timeout timeout in seconds. + * @return List of unique IP addresses corresponding to the domainNames. + */ + private LinkedHashMap<String, List<InetAddress>> getIP( + List<String> domainNames, int filter, Network network, long timeout) { + // LinkedHashMap preserves insertion order (and hence priority) of domain names passed in. + LinkedHashMap<String, List<InetAddress>> domainNameToIpAddr = new LinkedHashMap<>(); + + List<CompletableFuture<Map.Entry<String, List<InetAddress>>>> futuresList = + new ArrayList<>(); + for (String domainName : domainNames) { + if (InetAddresses.isNumericAddress(domainName)) { + Log.d(TAG, domainName + " is a numeric IP address!"); + InetAddress inetAddr = InetAddresses.parseNumericAddress(domainName); + domainNameToIpAddr.put(NO_DOMAIN, new ArrayList<>(List.of(inetAddr))); + continue; + } + + domainNameToIpAddr.put(domainName, new ArrayList<>()); + // Dispatches separate IPv4 and IPv6 queries to avoid being blocked on either result. + if (hasIpv4Address(network)) { + futuresList.add( + submitDnsResolverQuery( + domainName, network, DnsResolver.TYPE_A, mDnsResolutionExecutor)); + } + if (hasIpv6Address(network)) { + futuresList.add( + submitDnsResolverQuery( + domainName, + network, + DnsResolver.TYPE_AAAA, + mDnsResolutionExecutor)); + } + } + CompletableFuture<List<Map.Entry<String, List<InetAddress>>>> allFuturesResult = + allOf(futuresList); + + List<Map.Entry<String, List<InetAddress>>> resultList = null; + try { + resultList = allFuturesResult.get(timeout, TimeUnit.SECONDS); + } catch (ExecutionException e) { + Log.e(TAG, "Cause of ExecutionException: ", e.getCause()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + Log.e(TAG, "InterruptedException: ", e); + } catch (TimeoutException e) { + Log.e(TAG, "TimeoutException: ", e); + } finally { + if (resultList == null) { + Log.w(TAG, "No IP addresses in parallel DNS query!"); + } else { + for (Map.Entry<String, List<InetAddress>> entry : resultList) { + String resultDomainName = entry.getKey(); + List<InetAddress> resultIpAddr = v4v6ProtocolFilter(entry.getValue(), filter); + + if (!domainNameToIpAddr.containsKey(resultDomainName)) { + Log.w( + TAG, + "Unexpected domain name in DnsResolver result: " + + resultDomainName); + continue; + } + domainNameToIpAddr.get(resultDomainName).addAll(resultIpAddr); + } + } + } + return domainNameToIpAddr; + } + + /** + * Updates the validIpList with the IP addresses corresponding to this domainName. Runs blocking + * DNS resolution on the same thread. + * + * @param domainName Domain name for which DNS resolution needs to be performed. + * @param filter Selects for IPv4, IPv6 (or both) addresses from the resulting DNS records + * @param validIpList A running list of IP addresses that needs to be updated. + * @param network {@link Network} Network on which to run the DNS query. + */ private void getIP( - String domainName, int filter, ArrayList<InetAddress> validIpList, Network network) { - InetAddress[] ipList; + String domainName, int filter, List<InetAddress> validIpList, Network network) { + List<InetAddress> ipList = new ArrayList<InetAddress>(); // Get All IP for each domain name Log.d(TAG, "Input domainName : " + domainName); - try { - CompletableFuture<List<InetAddress>> result = new CompletableFuture(); - final DnsResolver.Callback<List<InetAddress>> cb = - new DnsResolver.Callback<List<InetAddress>>() { - @Override - public void onAnswer( - @NonNull final List<InetAddress> answer, final int rcode) { - if (rcode != 0) { - Log.e(TAG, "DnsResolver Response Code = " + rcode); + + if (InetAddresses.isNumericAddress(domainName)) { + Log.d(TAG, domainName + " is a numeric IP address!"); + ipList.add(InetAddresses.parseNumericAddress(domainName)); + } else { + try { + CompletableFuture<List<InetAddress>> result = new CompletableFuture(); + final DnsResolver.Callback<List<InetAddress>> cb = + new DnsResolver.Callback<List<InetAddress>>() { + @Override + public void onAnswer( + @NonNull final List<InetAddress> answer, final int rcode) { + if (rcode != 0) { + Log.e(TAG, "DnsResolver Response Code = " + rcode); + } + result.complete(answer); } - result.complete(answer); - } - @Override - public void onError(@Nullable final DnsResolver.DnsException error) { - Log.e(TAG, "Resolve DNS with error : " + error); - result.completeExceptionally(error); - } - }; - DnsResolver.getInstance() - .query(network, domainName, DnsResolver.FLAG_EMPTY, r -> r.run(), null, cb); - - // Filter the IP list by input ProtoFilter - for (InetAddress ipAddress : result.get()) { - switch (filter) { - case PROTO_FILTER_IPV4: - if (ipAddress instanceof Inet4Address) { - validIpList.add(ipAddress); - } - break; - case PROTO_FILTER_IPV6: - if (!IwlanHelper.isIpv4EmbeddedIpv6Address(ipAddress)) { - validIpList.add(ipAddress); - } - break; - case PROTO_FILTER_IPV4V6: - validIpList.add(ipAddress); - break; - default: - Log.d(TAG, "Invalid ProtoFilter : " + filter); + @Override + public void onError(@Nullable final DnsResolver.DnsException error) { + Log.e(TAG, "Resolve DNS with error : " + error); + result.completeExceptionally(error); + } + }; + DnsResolver.getInstance() + .query( + network, + domainName, + DnsResolver.FLAG_EMPTY, + Runnable::run, + null, + cb); + ipList = + new ArrayList<>( + result.get(DNS_RESOLVER_TIMEOUT_DURATION_SEC, TimeUnit.SECONDS)); + } catch (ExecutionException e) { + Log.e(TAG, "Cause of ExecutionException: ", e.getCause()); + } catch (InterruptedException e) { + Thread thread = Thread.currentThread(); + if (thread.interrupted()) { + thread.interrupt(); } + Log.e(TAG, "InterruptedException: ", e); + } catch (TimeoutException e) { + Log.e(TAG, "TimeoutException: ", e); } - } catch (Exception e) { - Log.e(TAG, "Exception when resolving domainName : " + domainName + ".", e); } + + List<InetAddress> filteredIpList = v4v6ProtocolFilter(ipList, filter); + validIpList.addAll(filteredIpList); } private String[] getPlmnList() { - List<String> plmnsFromSubInfo = new ArrayList<>(); - - List<String> plmnsFromCarrierConfig = - new ArrayList<>( - Arrays.asList( - IwlanHelper.getConfig( - CarrierConfigManager.Iwlan.KEY_MCC_MNCS_STRING_ARRAY, - mContext, - mSlotId))); + List<String> plmnsFromCarrierConfig = getPlmnsFromCarrierConfig(); Log.d(TAG, "plmnsFromCarrierConfig:" + plmnsFromCarrierConfig); // Get Ehplmns & mccmnc from SubscriptionManager @@ -223,59 +460,72 @@ public class EpdgSelector { return plmnsFromCarrierConfig.toArray(new String[plmnsFromCarrierConfig.size()]); } - // There are three sources of plmns - sim plmn, plmn list from carrier config and - // Ehplmn list from subscription info. - // The plmns are prioritized as follows: - // 1. Sim plmn - // 2. Plmns common to both lists. - // 3. Remaining plmns in the lists. - List<String> combinedList = new ArrayList<>(); // Get MCCMNC from IMSI - String plmnFromImsi = - new StringBuilder() - .append(subInfo.getMccString()) - .append("-") - .append(subInfo.getMncString()) - .toString(); - combinedList.add(plmnFromImsi); - - // Get Ehplmns from TelephonyManager - for (String ehplmn : getEhplmns()) { - if (ehplmn.length() == 5 || ehplmn.length() == 6) { - StringBuilder str = new StringBuilder(ehplmn); - str.insert(3, "-"); - plmnsFromSubInfo.add(str.toString()); - } - } + String plmnFromImsi = subInfo.getMccString() + subInfo.getMncString(); - Log.d(TAG, "plmnsFromSubInfo:" + plmnsFromSubInfo); + int[] prioritizedPlmnTypes = + IwlanHelper.getConfig( + CarrierConfigManager.Iwlan.KEY_EPDG_PLMN_PRIORITY_INT_ARRAY, + mContext, + mSlotId); - // To avoid double adding plmn from imsi - plmnsFromCarrierConfig.removeIf(i -> i.equals(plmnFromImsi)); - plmnsFromSubInfo.removeIf(i -> i.equals(plmnFromImsi)); + List<String> ehplmns = getEhplmns(); + String registeredPlmn = getRegisteredPlmn(); - for (Iterator<String> iterator = plmnsFromCarrierConfig.iterator(); iterator.hasNext(); ) { - String plmn = iterator.next(); - if (plmnsFromSubInfo.contains(plmn)) { - combinedList.add(plmn); - plmnsFromSubInfo.remove(plmn); - iterator.remove(); + List<String> combinedList = new ArrayList<>(); + for (int plmnType : prioritizedPlmnTypes) { + switch (plmnType) { + case CarrierConfigManager.Iwlan.EPDG_PLMN_RPLMN: + if (isInEpdgSelectionInfo(registeredPlmn)) { + combinedList.add(registeredPlmn); + } + break; + case CarrierConfigManager.Iwlan.EPDG_PLMN_HPLMN: + combinedList.add(plmnFromImsi); + break; + case CarrierConfigManager.Iwlan.EPDG_PLMN_EHPLMN_ALL: + combinedList.addAll(getEhplmns()); + break; + case CarrierConfigManager.Iwlan.EPDG_PLMN_EHPLMN_FIRST: + if (!ehplmns.isEmpty()) { + combinedList.add(ehplmns.get(0)); + } + break; + default: + Log.e(TAG, "Unknown PLMN type: " + plmnType); + break; } } - combinedList.addAll(plmnsFromSubInfo); - combinedList.addAll(plmnsFromCarrierConfig); + combinedList = + combinedList.stream() + .distinct() + .filter(EpdgSelector::isValidPlmn) + .map(plmn -> new StringBuilder(plmn).insert(3, "-").toString()) + .toList(); Log.d(TAG, "Final plmn list:" + combinedList); return combinedList.toArray(new String[combinedList.size()]); } - private ArrayList<InetAddress> removeDuplicateIp(ArrayList<InetAddress> validIpList) { - ArrayList<InetAddress> resultIpList = new ArrayList<InetAddress>(); + private List<String> getPlmnsFromCarrierConfig() { + return Arrays.asList( + IwlanHelper.getConfig( + CarrierConfigManager.Iwlan.KEY_MCC_MNCS_STRING_ARRAY, mContext, mSlotId)); + } - for (Iterator<InetAddress> iterator = validIpList.iterator(); iterator.hasNext(); ) { - InetAddress validIp = iterator.next(); + private boolean isInEpdgSelectionInfo(String plmn) { + if (!isValidPlmn(plmn)) { + return false; + } + List<String> plmnsFromCarrierConfig = getPlmnsFromCarrierConfig(); + return plmnsFromCarrierConfig.contains(new StringBuilder(plmn).insert(3, "-").toString()); + } + private ArrayList<InetAddress> removeDuplicateIp(List<InetAddress> validIpList) { + ArrayList<InetAddress> resultIpList = new ArrayList<InetAddress>(); + + for (InetAddress validIp : validIpList) { if (!resultIpList.contains(validIp)) { resultIpList.add(validIp); } @@ -284,16 +534,51 @@ 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: + Log.w(TAG, "Invalid EpdgAddressOrder : " + order); + } + } + private String[] splitMccMnc(String plmn) { String[] mccmnc = plmn.split("-"); mccmnc[1] = String.format("%03d", Integer.parseInt(mccmnc[1])); return mccmnc; } + /** + * @return the registered PLMN, null if not registered with 3gpp or failed to get telephony + * manager + */ + @Nullable + private String getRegisteredPlmn() { + TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class); + if (telephonyManager == null) { + Log.e(TAG, "TelephonyManager is NULL"); + return null; + } + + telephonyManager = + telephonyManager.createForSubscriptionId(IwlanHelper.getSubId(mContext, mSlotId)); + + String registeredPlmn = telephonyManager.getNetworkOperator(); + return registeredPlmn.isEmpty() ? null : registeredPlmn; + } + private List<String> getEhplmns() { TelephonyManager mTelephonyManager = mContext.getSystemService(TelephonyManager.class); mTelephonyManager = - mTelephonyManager.createForSubscriptionId(IwlanHelper.getSubId(mContext, mSlotId)); + Objects.requireNonNull(mTelephonyManager) + .createForSubscriptionId(IwlanHelper.getSubId(mContext, mSlotId)); if (mTelephonyManager == null) { Log.e(TAG, "TelephonyManager is NULL"); @@ -304,14 +589,14 @@ public class EpdgSelector { } private void resolutionMethodStatic( - int filter, ArrayList<InetAddress> validIpList, boolean isRoaming, Network network) { + int filter, List<InetAddress> validIpList, Network network) { String[] domainNames = null; Log.d(TAG, "STATIC Method"); // Get the static domain names from carrier config // Config obtained in form of a list of domain names separated by - // a delimeter is only used for testing purpose. + // a delimiter is only used for testing purpose. if (!inSameCountry()) { domainNames = getDomainNames( @@ -327,9 +612,14 @@ public class EpdgSelector { } Log.d(TAG, "Static Domain Names: " + Arrays.toString(domainNames)); - for (String domainName : domainNames) { - getIP(domainName, filter, validIpList, network); - } + LinkedHashMap<String, List<InetAddress>> domainNameToIpAddr = + getIP( + Arrays.asList(domainNames), + filter, + network, + PARALLEL_STATIC_RESOLUTION_TIMEOUT_DURATION_SEC); + printParallelDnsResult(domainNameToIpAddr); + domainNameToIpAddr.values().forEach(validIpList::addAll); } private String[] getDomainNames(String key) { @@ -345,7 +635,9 @@ public class EpdgSelector { boolean inSameCountry = true; TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); - tm = tm.createForSubscriptionId(IwlanHelper.getSubId(mContext, mSlotId)); + tm = + Objects.requireNonNull(tm) + .createForSubscriptionId(IwlanHelper.getSubId(mContext, mSlotId)); if (tm != null) { String simCountry = tm.getSimCountryIso(); @@ -359,14 +651,15 @@ public class EpdgSelector { return inSameCountry; } - private void resolutionMethodPlmn( - int filter, ArrayList<InetAddress> validIpList, boolean isEmergency, Network network) { + private Map<String, List<InetAddress>> resolutionMethodPlmn( + int filter, List<InetAddress> validIpList, boolean isEmergency, Network network) { String[] plmnList; StringBuilder domainName = new StringBuilder(); Log.d(TAG, "PLMN Method"); plmnList = getPlmnList(); + List<String> domainNames = new ArrayList<>(); for (String plmn : plmnList) { String[] mccmnc = splitMccMnc(plmn); /* @@ -377,22 +670,38 @@ public class EpdgSelector { * sos.epdg.epc.mnc<MNC>.mcc<MCC>.pub.3gppnetwork.org */ if (isEmergency) { - domainName.append("sos."); + domainName = new StringBuilder(); + domainName + .append("sos.") + .append("epdg.epc.mnc") + .append(mccmnc[1]) + .append(".mcc") + .append(mccmnc[0]) + .append(".pub.3gppnetwork.org"); + domainNames.add(domainName.toString()); + domainName.setLength(0); } - + // For emergency PDN setup, still adding FQDN without "sos" header as second priority + // because some operator doesn't support hostname with "sos" prefix. domainName .append("epdg.epc.mnc") .append(mccmnc[1]) .append(".mcc") .append(mccmnc[0]) .append(".pub.3gppnetwork.org"); - getIP(domainName.toString(), filter, validIpList, network); + domainNames.add(domainName.toString()); domainName.setLength(0); } + + LinkedHashMap<String, List<InetAddress>> domainNameToIpAddr = + getIP(domainNames, filter, network, PARALLEL_PLMN_RESOLUTION_TIMEOUT_DURATION_SEC); + printParallelDnsResult(domainNameToIpAddr); + domainNameToIpAddr.values().forEach(validIpList::addAll); + return domainNameToIpAddr; } private void resolutionMethodCellularLoc( - int filter, ArrayList<InetAddress> validIpList, boolean isEmergency, Network network) { + int filter, List<InetAddress> validIpList, boolean isEmergency, Network network) { String[] plmnList; StringBuilder domainName = new StringBuilder(); @@ -400,7 +709,8 @@ public class EpdgSelector { TelephonyManager mTelephonyManager = mContext.getSystemService(TelephonyManager.class); mTelephonyManager = - mTelephonyManager.createForSubscriptionId(IwlanHelper.getSubId(mContext, mSlotId)); + Objects.requireNonNull(mTelephonyManager) + .createForSubscriptionId(IwlanHelper.getSubId(mContext, mSlotId)); if (mTelephonyManager == null) { Log.e(TAG, "TelephonyManager is NULL"); @@ -466,8 +776,7 @@ public class EpdgSelector { domainName.setLength(0); } } else if (cellInfo instanceof CellInfoNr) { - CellIdentityNr nrCellId = - (CellIdentityNr) ((CellInfoNr) cellInfo).getCellIdentity(); + CellIdentityNr nrCellId = (CellIdentityNr) cellInfo.getCellIdentity(); String tacString = String.format("%06x", nrCellId.getTac()); String[] tacSubString = new String[3]; tacSubString[0] = tacString.substring(0, 2); @@ -514,7 +823,7 @@ public class EpdgSelector { private void lacDomainNameResolution( int filter, - ArrayList<InetAddress> validIpList, + List<InetAddress> validIpList, String lacString, boolean isEmergency, Network network) { @@ -548,7 +857,7 @@ public class EpdgSelector { } } - private void resolutionMethodPco(int filter, ArrayList<InetAddress> validIpList) { + private void resolutionMethodPco(int filter, List<InetAddress> validIpList) { Log.d(TAG, "PCO Method"); int PCO_ID_IPV6 = @@ -586,7 +895,7 @@ public class EpdgSelector { } } - private void getInetAddressWithPcoData(byte[] pcoData, ArrayList<InetAddress> validIpList) { + private void getInetAddressWithPcoData(byte[] pcoData, List<InetAddress> validIpList) { InetAddress ipAddress; if (pcoData != null && pcoData.length > 0) { try { @@ -647,7 +956,7 @@ public class EpdgSelector { private void processNaptrResponse( int filter, - ArrayList<InetAddress> validIpList, + List<InetAddress> validIpList, boolean isEmergency, Network network, boolean isRegisteredWith3GPP, @@ -699,12 +1008,13 @@ public class EpdgSelector { } private void resolutionMethodVisitedCountry( - int filter, ArrayList<InetAddress> validIpList, boolean isEmergency, Network network) { + int filter, List<InetAddress> validIpList, boolean isEmergency, Network network) { StringBuilder domainName = new StringBuilder(); TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class); telephonyManager = - telephonyManager.createForSubscriptionId(IwlanHelper.getSubId(mContext, mSlotId)); + Objects.requireNonNull(telephonyManager) + .createForSubscriptionId(IwlanHelper.getSubId(mContext, mSlotId)); if (telephonyManager == null) { Log.e(TAG, "TelephonyManager is NULL"); @@ -724,8 +1034,7 @@ public class EpdgSelector { final String cellMcc = telephonyManager.getNetworkOperator().substring(0, 3); final String cellMnc = telephonyManager.getNetworkOperator().substring(3); - final String plmnFromNetwork = - new StringBuilder().append(cellMcc).append("-").append(cellMnc).toString(); + final String plmnFromNetwork = cellMcc + "-" + cellMnc; final String registeredhostName = composeFqdnWithMccMnc(cellMcc, cellMnc, isEmergency); /* @@ -755,7 +1064,7 @@ public class EpdgSelector { .append(cellMcc) .append(".visited-country.pub.3gppnetwork.org"); - Log.d(TAG, "Visited Country FQDN with " + domainName.toString()); + Log.d(TAG, "Visited Country FQDN with " + domainName); CompletableFuture<List<NaptrTarget>> naptrDnsResult = new CompletableFuture<>(); DnsResolver.Callback<List<NaptrTarget>> naptrDnsCb = @@ -774,10 +1083,11 @@ public class EpdgSelector { naptrDnsResult.completeExceptionally(error); } }; - NaptrDnsResolver.query(network, domainName.toString(), r -> r.run(), null, naptrDnsCb); + NaptrDnsResolver.query(network, domainName.toString(), Runnable::run, null, naptrDnsCb); try { - final List<NaptrTarget> naptrResponse = naptrDnsResult.get(); + final List<NaptrTarget> naptrResponse = + naptrDnsResult.get(DNS_RESOLVER_TIMEOUT_DURATION_SEC, TimeUnit.SECONDS); // Check if there is any record in the NAPTR response if (naptrResponse != null && naptrResponse.size() > 0) { processNaptrResponse( @@ -793,27 +1103,74 @@ public class EpdgSelector { } catch (ExecutionException e) { Log.e(TAG, "Cause of ExecutionException: ", e.getCause()); } catch (InterruptedException e) { - if (Thread.currentThread().interrupted()) { - Thread.currentThread().interrupt(); + Thread thread = Thread.currentThread(); + if (thread.interrupted()) { + thread.interrupt(); } Log.e(TAG, "InterruptedException: ", e); + } catch (TimeoutException e) { + Log.e(TAG, "TimeoutException: ", e); } } + // Cancels duplicate prefetches a prefetch is already running. Always schedules tunnel bringup. + private void trySubmitEpdgSelectionExecutor( + Runnable runnable, boolean isPrefetch, boolean isEmergency) { + if (isEmergency) { + if (isPrefetch) { + if (mSosDnsPrefetchFuture == null || mSosDnsPrefetchFuture.isDone()) { + mSosDnsPrefetchFuture = mSosEpdgSelectionExecutor.submit(runnable); + } + } else { + mSosEpdgSelectionExecutor.execute(runnable); + } + } else { + if (isPrefetch) { + if (mDnsPrefetchFuture == null || mDnsPrefetchFuture.isDone()) { + mDnsPrefetchFuture = mEpdgSelectionExecutor.submit(runnable); + } + } else { + mEpdgSelectionExecutor.execute(runnable); + } + } + } + + /** + * Asynchronously runs DNS resolution on a carrier-specific list of ePDG servers into IP + * addresses, and passes them to the caller via the {@link EpdgSelectorCallback}. + * + * @param transactionId A unique ID passed in to match the response with the request. If this + * value is 0, the caller is not interested in the result. + * @param filter Allows the caller to filter for IPv4 or IPv6 servers, or both. + * @param isRoaming Specifies whether the subscription is currently in roaming state. + * @param isEmergency Specifies whether the ePDG server lookup is to make an emergency call. + * @param network {@link Network} The server lookups will be performed over this Network. + * @param selectorCallback {@link EpdgSelectorCallback} The result will be returned through this + * callback. If null, the caller is not interested in the result. Typically, this means the + * caller is performing DNS prefetch of the ePDG server addresses to warm the native + * dnsresolver module's caches. + * @return {link IwlanError} denoting the status of this operation. + */ public IwlanError getValidatedServerList( int transactionId, @ProtoFilter int filter, + @EpdgAddressOrder int order, boolean isRoaming, boolean isEmergency, @NonNull Network network, EpdgSelectorCallback selectorCallback) { - ArrayList<InetAddress> validIpList = new ArrayList<InetAddress>(); - StringBuilder domainName = new StringBuilder(); - Runnable doValidation = + final Runnable epdgSelectionRunnable = () -> { - Log.d(TAG, "Processing request with transactionId: " + transactionId); - String[] plmnList; + List<InetAddress> validIpList = new ArrayList<>(); + Log.d( + TAG, + "Processing request with transactionId: " + + transactionId + + ", for slotID: " + + mSlotId + + ", isEmergency: " + + isEmergency); int[] addrResolutionMethods = IwlanHelper.getConfig( @@ -834,14 +1191,17 @@ public class EpdgSelector { resolutionMethodVisitedCountry(filter, validIpList, isEmergency, network); } + Map<String, List<InetAddress>> plmnDomainNamesToIpAddress = null; for (int addrResolutionMethod : addrResolutionMethods) { switch (addrResolutionMethod) { case CarrierConfigManager.Iwlan.EPDG_ADDRESS_STATIC: - resolutionMethodStatic(filter, validIpList, isRoaming, network); + resolutionMethodStatic(filter, validIpList, network); break; case CarrierConfigManager.Iwlan.EPDG_ADDRESS_PLMN: - resolutionMethodPlmn(filter, validIpList, isEmergency, network); + plmnDomainNamesToIpAddress = + resolutionMethodPlmn( + filter, validIpList, isEmergency, network); break; case CarrierConfigManager.Iwlan.EPDG_ADDRESS_PCO: @@ -862,8 +1222,29 @@ public class EpdgSelector { } if (selectorCallback != null) { + if (mErrorPolicyManager.getMostRecentDataFailCause() + == DataFailCause.IWLAN_CONGESTION) { + Objects.requireNonNull(plmnDomainNamesToIpAddress) + .values() + .removeIf(List::isEmpty); + + int numFqdns = plmnDomainNamesToIpAddress.size(); + int index = mErrorPolicyManager.getCurrentFqdnIndex(numFqdns); + if (index >= 0 && index < numFqdns) { + Object[] keys = plmnDomainNamesToIpAddress.keySet().toArray(); + validIpList = plmnDomainNamesToIpAddress.get((String) keys[index]); + } else { + Log.w( + TAG, + "CONGESTION error handling- invalid index: " + + index + + " number of PLMN FQDNs: " + + numFqdns); + } + } + if (!validIpList.isEmpty()) { - Collections.sort(validIpList, inetAddressComparator); + prioritizeIp(validIpList, order); selectorCallback.onServerListChanged( transactionId, removeDuplicateIp(validIpList)); } else { @@ -874,8 +1255,20 @@ public class EpdgSelector { } } }; - Thread subThread = new Thread(doValidation); - subThread.start(); + + boolean isPrefetch = (selectorCallback == null); + trySubmitEpdgSelectionExecutor(epdgSelectionRunnable, isPrefetch, isEmergency); + return new IwlanError(IwlanError.NO_ERROR); } + + /** + * Validates a PLMN (Public Land Mobile Network) identifier string. + * + * @param plmn The PLMN identifier string to validate. + * @return True if the PLMN identifier is valid, false otherwise. + */ + private static boolean isValidPlmn(String plmn) { + return plmn != null && plmn.matches("\\d{5,6}"); + } } diff --git a/src/com/google/android/iwlan/epdg/EpdgTunnelManager.java b/src/com/google/android/iwlan/epdg/EpdgTunnelManager.java index ea78893..b8b70e4 100644 --- a/src/com/google/android/iwlan/epdg/EpdgTunnelManager.java +++ b/src/com/google/android/iwlan/epdg/EpdgTunnelManager.java @@ -28,6 +28,7 @@ import android.net.IpPrefix; import android.net.IpSecManager; import android.net.IpSecTransform; import android.net.LinkAddress; +import android.net.LinkProperties; import android.net.Network; import android.net.eap.EapAkaInfo; import android.net.eap.EapInfo; @@ -50,6 +51,7 @@ import android.net.ipsec.ike.IkeTrafficSelector; import android.net.ipsec.ike.SaProposal; import android.net.ipsec.ike.TunnelModeChildSessionParams; import android.net.ipsec.ike.exceptions.IkeException; +import android.net.ipsec.ike.exceptions.IkeIOException; import android.net.ipsec.ike.exceptions.IkeProtocolException; import android.net.ipsec.ike.ike3gpp.Ike3gppBackoffTimer; import android.net.ipsec.ike.ike3gpp.Ike3gppData; @@ -74,9 +76,12 @@ import com.android.internal.annotations.VisibleForTesting; import com.google.android.iwlan.ErrorPolicyManager; import com.google.android.iwlan.IwlanError; import com.google.android.iwlan.IwlanHelper; +import com.google.android.iwlan.IwlanTunnelMetricsImpl; +import com.google.android.iwlan.TunnelMetricsInterface; +import com.google.android.iwlan.TunnelMetricsInterface.OnClosedMetrics; +import com.google.android.iwlan.TunnelMetricsInterface.OnOpenedMetrics; import com.google.android.iwlan.exceptions.IwlanSimNotReadyException; -import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.net.Inet4Address; @@ -85,11 +90,10 @@ import java.net.InetAddress; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -99,9 +103,8 @@ import java.util.concurrent.TimeUnit; public class EpdgTunnelManager { - private Context mContext; + private final Context mContext; private final int mSlotId; - private HandlerThread mHandlerThread; private Handler mHandler; private static final int EVENT_TUNNEL_BRINGUP_REQUEST = 0; @@ -115,6 +118,7 @@ public class EpdgTunnelManager { private static final int EVENT_UPDATE_NETWORK = 9; private static final int EVENT_IKE_SESSION_OPENED = 10; private static final int EVENT_IKE_SESSION_CONNECTION_INFO_CHANGED = 11; + private static final int EVENT_IKE_3GPP_DATA_RECEIVED = 12; private static final int IKE_HARD_LIFETIME_SEC_MINIMUM = 300; private static final int IKE_HARD_LIFETIME_SEC_MAXIMUM = 86400; private static final int IKE_SOFT_LIFETIME_SEC_MINIMUM = 120; @@ -143,26 +147,35 @@ public class EpdgTunnelManager { private static final String TRAFFIC_SELECTOR_IPV6_END_ADDR = "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"; - private static Map<Integer, EpdgTunnelManager> mTunnelManagerInstances = + // "192.0.2.0" is selected from RFC5737, "IPv4 Address Blocks Reserved for Documentation" + private static final InetAddress DUMMY_ADDR = InetAddresses.parseNumericAddress("192.0.2.0"); + + private static final Map<Integer, EpdgTunnelManager> mTunnelManagerInstances = new ConcurrentHashMap<>(); - private Queue<TunnelRequestWrapper> mRequestQueue = new LinkedList<>(); + private Queue<TunnelRequestWrapper> mPendingBringUpRequests = new LinkedList<>(); + + private final EpdgInfo mValidEpdgInfo = new EpdgInfo(); + @Nullable private InetAddress mEpdgAddress; + + // The most recently updated system default network as seen by IwlanDataService. + @Nullable private Network mDefaultNetwork; + // The latest Network provided to the IKE session. Only for debugging purposes. + @Nullable private Network mIkeSessionNetwork; - private EpdgInfo mValidEpdgInfo = new EpdgInfo(); - private InetAddress mEpdgAddress; - private Network mNetwork; private int mTransactionId = 0; - private int mProtoFilter = EpdgSelector.PROTO_FILTER_IPV4V6; - private boolean mIsEpdgAddressSelected; - private IkeSessionCreator mIkeSessionCreator; + private boolean mHasConnectedToEpdg; + private final IkeSessionCreator mIkeSessionCreator; private Map<String, TunnelConfig> mApnNameToTunnelConfig = new ConcurrentHashMap<>(); + private final Map<String, Integer> mApnNameToCurrentToken = new ConcurrentHashMap<>(); private final String TAG; - private List<InetAddress> mLocalAddresses; - @Nullable private byte[] mNextReauthId = null; + private long mEpdgServerSelectionDuration = 0; + private long mEpdgServerSelectionStartTime = 0; + private long mIkeTunnelEstablishmentStartTime = 0; private static final Set<Integer> VALID_DH_GROUPS; private static final Set<Integer> VALID_KEY_LENGTHS; @@ -171,60 +184,49 @@ public class EpdgTunnelManager { private static final Set<Integer> VALID_ENCRYPTION_ALGOS; private static final String CONFIG_TYPE_DH_GROUP = "dh group"; - private static final String CONFIG_TYPE_KEY_LEN = "alogrithm key length"; + private static final String CONFIG_TYPE_KEY_LEN = "algorithm key length"; private static final String CONFIG_TYPE_PRF_ALGO = "prf algorithm"; private static final String CONFIG_TYPE_INTEGRITY_ALGO = "integrity algorithm"; private static final String CONFIG_TYPE_ENCRYPT_ALGO = "encryption algorithm"; static { VALID_DH_GROUPS = - Collections.unmodifiableSet( - new HashSet<>( - Arrays.asList( - SaProposal.DH_GROUP_1024_BIT_MODP, - SaProposal.DH_GROUP_1536_BIT_MODP, - SaProposal.DH_GROUP_2048_BIT_MODP))); + Set.of( + SaProposal.DH_GROUP_1024_BIT_MODP, + SaProposal.DH_GROUP_1536_BIT_MODP, + SaProposal.DH_GROUP_2048_BIT_MODP); VALID_KEY_LENGTHS = - Collections.unmodifiableSet( - new HashSet<>( - Arrays.asList( - SaProposal.KEY_LEN_AES_128, - SaProposal.KEY_LEN_AES_192, - SaProposal.KEY_LEN_AES_256))); + Set.of( + SaProposal.KEY_LEN_AES_128, + SaProposal.KEY_LEN_AES_192, + SaProposal.KEY_LEN_AES_256); VALID_ENCRYPTION_ALGOS = - Collections.unmodifiableSet( - new HashSet<>( - Arrays.asList( - SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, - SaProposal.ENCRYPTION_ALGORITHM_AES_CTR))); + Set.of( + SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, + SaProposal.ENCRYPTION_ALGORITHM_AES_CTR); VALID_INTEGRITY_ALGOS = - Collections.unmodifiableSet( - new HashSet<>( - Arrays.asList( - SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96, - SaProposal.INTEGRITY_ALGORITHM_AES_XCBC_96, - SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128, - SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_384_192, - SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_512_256))); + Set.of( + SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96, + SaProposal.INTEGRITY_ALGORITHM_AES_XCBC_96, + SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128, + SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_384_192, + SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_512_256); VALID_PRF_ALGOS = - Collections.unmodifiableSet( - new HashSet<>( - Arrays.asList( - SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1, - SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC, - SaProposal.PSEUDORANDOM_FUNCTION_SHA2_256, - SaProposal.PSEUDORANDOM_FUNCTION_SHA2_384, - SaProposal.PSEUDORANDOM_FUNCTION_SHA2_512))); + Set.of( + SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1, + SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC, + SaProposal.PSEUDORANDOM_FUNCTION_SHA2_256, + SaProposal.PSEUDORANDOM_FUNCTION_SHA2_384, + SaProposal.PSEUDORANDOM_FUNCTION_SHA2_512); } private final EpdgSelector.EpdgSelectorCallback mSelectorCallback = new EpdgSelector.EpdgSelectorCallback() { @Override - public void onServerListChanged( - int transactionId, ArrayList<InetAddress> validIPList) { + public void onServerListChanged(int transactionId, List<InetAddress> validIPList) { sendSelectionRequestComplete( validIPList, new IwlanError(IwlanError.NO_ERROR), transactionId); } @@ -238,17 +240,28 @@ public class EpdgTunnelManager { @VisibleForTesting class TunnelConfig { @NonNull final TunnelCallback mTunnelCallback; + @NonNull final TunnelMetricsInterface mTunnelMetrics; // TODO: Change this to TunnelLinkProperties after removing autovalue private List<InetAddress> mPcscfAddrList; private List<InetAddress> mDnsAddrList; private List<LinkAddress> mInternalAddrList; - private InetAddress mSrcIpv6Address; - private int mSrcIpv6AddressPrefixLen; + private final InetAddress mSrcIpv6Address; + private final int mSrcIpv6AddressPrefixLen; private NetworkSliceInfo mSliceInfo; private boolean mIsBackoffTimeValid = false; private long mBackoffTime; + private IkeSessionState mIkeSessionState; + + public IkeSessionState getIkeSessionState() { + return mIkeSessionState; + } + + public void setIkeSessionState(IkeSessionState ikeSessionState) { + mIkeSessionState = ikeSessionState; + } + public NetworkSliceInfo getSliceInfo() { return mSliceInfo; } @@ -277,13 +290,17 @@ public class EpdgTunnelManager { public TunnelConfig( IkeSession ikeSession, TunnelCallback tunnelCallback, + TunnelMetricsInterface tunnelMetrics, InetAddress srcIpv6Addr, int srcIpv6PrefixLength) { mTunnelCallback = tunnelCallback; + mTunnelMetrics = tunnelMetrics; mIkeSession = ikeSession; mError = new IwlanError(IwlanError.NO_ERROR); mSrcIpv6Address = srcIpv6Addr; mSrcIpv6AddressPrefixLen = srcIpv6PrefixLength; + + setIkeSessionState(IkeSessionState.IKE_SESSION_INIT_IN_PROGRESS); } @NonNull @@ -291,6 +308,11 @@ public class EpdgTunnelManager { return mTunnelCallback; } + @NonNull + TunnelMetricsInterface getTunnelMetrics() { + return mTunnelMetrics; + } + List<InetAddress> getPcscfAddrList() { return mPcscfAddrList; } @@ -315,9 +337,7 @@ public class EpdgTunnelManager { if (laddr.isIpv6() && (laddr.getPrefixLength() == mSrcIpv6AddressPrefixLen)) { IpPrefix assignedPrefix = new IpPrefix(laddr.getAddress(), laddr.getPrefixLength()); IpPrefix srcPrefix = new IpPrefix(mSrcIpv6Address, mSrcIpv6AddressPrefixLen); - if (assignedPrefix.equals(srcPrefix)) { - return true; - } + return assignedPrefix.equals(srcPrefix); } return false; } @@ -325,7 +345,7 @@ public class EpdgTunnelManager { public void setInternalAddrList(List<LinkAddress> internalAddrList) { mInternalAddrList = new ArrayList<LinkAddress>(internalAddrList); if (getSrcIpv6Address() != null) { - // check if we can reuse src ipv6 address (i.e if prefix is same) + // check if we can reuse src ipv6 address (i.e. if prefix is same) for (LinkAddress assignedAddr : internalAddrList) { if (isPrefixSameAsSrcIP(assignedAddr)) { // the assigned IPv6 address is same as pre-Handover IPv6 @@ -374,21 +394,6 @@ public class EpdgTunnelManager { return mSrcIpv6Address; } - public int getSrcIpv6AddressPrefixLen() { - return mSrcIpv6AddressPrefixLen; - } - - private String addressListString(List<InetAddress> list) { - StringBuilder sb = new StringBuilder(); - sb.append("{ "); - for (InetAddress addr : list) { - sb.append(addr); - sb.append(", "); - } - sb.append(" }"); - return sb.toString(); - } - public boolean hasTunnelOpened() { return mInternalAddrList != null && !mInternalAddrList.isEmpty() /* The child session is opened */ @@ -399,34 +404,13 @@ public class EpdgTunnelManager { public String toString() { StringBuilder sb = new StringBuilder(); sb.append("TunnelConfig { "); - /*if (mPcscfAddrList != null) { - sb.append("mPcscfAddrList: " + addressListString(mPcscfAddrList)); - sb.append(", "); - } - if (mDnsAddrList != null) { - sb.append("mDnsAddrList: " + addressListString(mDnsAddrList)); - sb.append(", "); - } - if (mInternalAddrList != null) { - sb.append("mInternalAddrList: { "); - for (LinkAddress addr : mInternalAddrList) { - sb.append(addr + ", "); - } - sb.append(" }, "); - } - - if (mSrcIpv6Address != null) { - sb.append("{mSrcIpv6Address: " + mSrcIpv6Address + "}, "); - } else { - sb.append("{NULL mSrcIpv6Address}, "); - } */ if (mSliceInfo != null) { - sb.append("mSliceInfo: " + mSliceInfo + ", "); + sb.append("mSliceInfo: ").append(mSliceInfo).append(", "); } if (mIsBackoffTimeValid) { - sb.append("mBackoffTime: " + mBackoffTime + ", "); + sb.append("mBackoffTime: ").append(mBackoffTime).append(", "); } sb.append(" }"); return sb.toString(); @@ -437,38 +421,40 @@ public class EpdgTunnelManager { class TmIkeSessionCallback implements IkeSessionCallback { private final String mApnName; + private final int mToken; - TmIkeSessionCallback(String apnName) { + TmIkeSessionCallback(String apnName, int token) { this.mApnName = apnName; + this.mToken = token; } @Override public void onOpened(IkeSessionConfiguration sessionConfiguration) { - Log.d(TAG, "Ike session opened for apn: " + mApnName); + Log.d(TAG, "Ike session opened for apn: " + mApnName + " with token: " + mToken); mHandler.sendMessage( mHandler.obtainMessage( EVENT_IKE_SESSION_OPENED, - new IkeSessionOpenedData(mApnName, sessionConfiguration))); + new IkeSessionOpenedData(mApnName, mToken, sessionConfiguration))); } @Override public void onClosed() { - Log.d(TAG, "Ike session closed for apn: " + mApnName); + Log.d(TAG, "Ike session closed for apn: " + mApnName + " with token: " + mToken); mHandler.sendMessage( mHandler.obtainMessage( EVENT_IKE_SESSION_CLOSED, - new SessionClosedData(mApnName, new IwlanError(IwlanError.NO_ERROR)))); + new SessionClosedData(mApnName, mToken, null /* ikeException */))); } @Override - public void onClosedExceptionally(IkeException exception) { + public void onClosedWithException(IkeException exception) { mNextReauthId = null; - onSessionClosedWithException(exception, mApnName, EVENT_IKE_SESSION_CLOSED); + onSessionClosedWithException(exception, mApnName, mToken, EVENT_IKE_SESSION_CLOSED); } @Override public void onError(IkeProtocolException exception) { - Log.d(TAG, "Ike session onError for apn: " + mApnName); + Log.d(TAG, "Ike session onError for apn: " + mApnName + " with token: " + mToken); mNextReauthId = null; @@ -488,51 +474,34 @@ public class EpdgTunnelManager { TAG, "Ike session connection info changed for apn: " + mApnName + + " with token: " + + mToken + " Network: " + network); mHandler.sendMessage( mHandler.obtainMessage( EVENT_IKE_SESSION_CONNECTION_INFO_CHANGED, - new IkeSessionConnectionInfoData(mApnName, ikeSessionConnectionInfo))); + new IkeSessionConnectionInfoData( + mApnName, mToken, ikeSessionConnectionInfo))); } } @VisibleForTesting class TmIke3gppCallback implements Ike3gppExtension.Ike3gppDataListener { private final String mApnName; + private final int mToken; - private TmIke3gppCallback(String apnName) { + private TmIke3gppCallback(String apnName, int token) { mApnName = apnName; + mToken = token; } @Override public void onIke3gppDataReceived(List<Ike3gppData> payloads) { - if (payloads != null && !payloads.isEmpty()) { - TunnelConfig tunnelConfig = mApnNameToTunnelConfig.get(mApnName); - for (Ike3gppData payload : payloads) { - if (payload.getDataType() == DATA_TYPE_NOTIFY_N1_MODE_INFORMATION) { - Log.d(TAG, "Got payload DATA_TYPE_NOTIFY_N1_MODE_INFORMATION"); - NetworkSliceInfo si = - NetworkSliceSelectionAssistanceInformation.getSliceInfo( - ((Ike3gppN1ModeInformation) payload).getSnssai()); - if (si != null) { - tunnelConfig.setSliceInfo(si); - Log.d(TAG, "SliceInfo: " + si); - } - } else if (payload.getDataType() == DATA_TYPE_NOTIFY_BACKOFF_TIMER) { - Log.d(TAG, "Got payload DATA_TYPE_NOTIFY_BACKOFF_TIMER"); - long backoffTime = - decodeBackoffTime( - ((Ike3gppBackoffTimer) payload).getBackoffTimer()); - if (backoffTime > 0) { - tunnelConfig.setBackoffTime(backoffTime); - Log.d(TAG, "Backoff Timer: " + backoffTime); - } - } - } - } else { - Log.e(TAG, "Null or empty payloads received:"); - } + mHandler.sendMessage( + mHandler.obtainMessage( + EVENT_IKE_3GPP_DATA_RECEIVED, + new Ike3gppDataReceived(mApnName, mToken, payloads))); } } @@ -540,67 +509,93 @@ public class EpdgTunnelManager { class TmChildSessionCallback implements ChildSessionCallback { private final String mApnName; + private final int mToken; - TmChildSessionCallback(String apnName) { + TmChildSessionCallback(String apnName, int token) { this.mApnName = apnName; + this.mToken = token; } @Override public void onOpened(ChildSessionConfiguration sessionConfiguration) { + Log.d(TAG, "onOpened child session for apn: " + mApnName + " with token: " + mToken); mHandler.sendMessage( mHandler.obtainMessage( EVENT_CHILD_SESSION_OPENED, new TunnelOpenedData( mApnName, + mToken, sessionConfiguration.getInternalDnsServers(), sessionConfiguration.getInternalAddresses()))); } @Override public void onClosed() { - Log.d(TAG, "onClosed child session for apn: " + mApnName); + Log.d(TAG, "onClosed child session for apn: " + mApnName + " with token: " + mToken); mHandler.sendMessage( mHandler.obtainMessage( EVENT_CHILD_SESSION_CLOSED, - new SessionClosedData(mApnName, new IwlanError(IwlanError.NO_ERROR)))); + new SessionClosedData(mApnName, mToken, null /* ikeException */))); } @Override - public void onClosedExceptionally(IkeException exception) { - onSessionClosedWithException(exception, mApnName, EVENT_CHILD_SESSION_CLOSED); + public void onClosedWithException(IkeException exception) { + onSessionClosedWithException(exception, mApnName, mToken, EVENT_CHILD_SESSION_CLOSED); } @Override public void onIpSecTransformsMigrated( IpSecTransform inIpSecTransform, IpSecTransform outIpSecTransform) { // migration is similar to addition - Log.d(TAG, "Transforms migrated for apn: + " + mApnName); + Log.d(TAG, "Transforms migrated for apn: " + mApnName + " with token: " + mToken); mHandler.sendMessage( mHandler.obtainMessage( EVENT_IPSEC_TRANSFORM_CREATED, new IpsecTransformData( - inIpSecTransform, IpSecManager.DIRECTION_IN, mApnName))); + inIpSecTransform, + IpSecManager.DIRECTION_IN, + mApnName, + mToken))); mHandler.sendMessage( mHandler.obtainMessage( EVENT_IPSEC_TRANSFORM_CREATED, new IpsecTransformData( - outIpSecTransform, IpSecManager.DIRECTION_OUT, mApnName))); + outIpSecTransform, + IpSecManager.DIRECTION_OUT, + mApnName, + mToken))); } @Override public void onIpSecTransformCreated(IpSecTransform ipSecTransform, int direction) { - Log.d(TAG, "Transform created, direction: " + direction + ", apn:" + mApnName); + Log.d( + TAG, + "Transform created, direction: " + + direction + + ", apn: " + + mApnName + + ", token: " + + mToken); mHandler.sendMessage( mHandler.obtainMessage( EVENT_IPSEC_TRANSFORM_CREATED, - new IpsecTransformData(ipSecTransform, direction, mApnName))); + new IpsecTransformData(ipSecTransform, direction, mApnName, mToken))); } @Override public void onIpSecTransformDeleted(IpSecTransform ipSecTransform, int direction) { - Log.d(TAG, "Transform deleted, direction: " + direction + ", apn:" + mApnName); + Log.d( + TAG, + "Transform deleted, direction: " + + direction + + ", apn: " + + mApnName + + ", token: " + + mToken); mHandler.sendMessage( - mHandler.obtainMessage(EVENT_IPSEC_TRANSFORM_DELETED, ipSecTransform)); + mHandler.obtainMessage( + EVENT_IPSEC_TRANSFORM_DELETED, + new IpsecTransformData(ipSecTransform, direction, mApnName, mToken))); } } @@ -619,13 +614,13 @@ public class EpdgTunnelManager { @VisibleForTesting Looper getLooper() { - mHandlerThread = new HandlerThread("EpdgTunnelManagerThread"); - mHandlerThread.start(); - return mHandlerThread.getLooper(); + HandlerThread handlerThread = new HandlerThread("EpdgTunnelManagerThread"); + handlerThread.start(); + return handlerThread.getLooper(); } /** - * Gets a epdg tunnel manager instance. + * Gets a EpdgTunnelManager instance. * * @param context application context * @param subId subscription ID for the tunnel @@ -636,6 +631,11 @@ public class EpdgTunnelManager { subId, k -> new EpdgTunnelManager(context, subId)); } + @VisibleForTesting + public static void resetAllInstances() { + mTunnelManagerInstances.clear(); + } + public interface TunnelCallback { /** * Called when the tunnel is opened. @@ -655,33 +655,36 @@ public class EpdgTunnelManager { /** * Close tunnel for an apn. Confirmation of closing will be delivered in TunnelCallback that was - * provided in {@link #bringUpTunnel} + * provided in {@link #bringUpTunnel}. If no tunnel was available, callback will be delivered + * using client-provided provided tunnelCallback and iwlanTunnelMetrics * * @param apnName apn name * @param forceClose if true, results in local cleanup of tunnel - * @return true if params are valid and tunnel exists. False otherwise. + * @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 */ - public boolean closeTunnel(@NonNull String apnName, boolean forceClose) { + public void closeTunnel( + @NonNull String apnName, + boolean forceClose, + @NonNull TunnelCallback tunnelCallback, + @NonNull IwlanTunnelMetricsImpl iwlanTunnelMetrics) { mHandler.sendMessage( mHandler.obtainMessage( EVENT_TUNNEL_BRINGDOWN_REQUEST, - forceClose ? 1 : 0, - 0 /*not used*/, - apnName)); - return true; + new TunnelBringdownRequest( + apnName, forceClose, tunnelCallback, iwlanTunnelMetrics))); } /** * Update the local Network. This will trigger a revaluation for every tunnel for which tunnel * manager has state. * - * <p>Tunnels in bringup state will be for closed since IKE currently keeps retrying. - * - * <p>For rest of the tunnels, update IKE session wth new network. This will either result in - * MOBIKE callflow or just a rekey over new Network + * @param network the network to be updated + * @param network the linkProperties to be updated */ - public void updateNetwork(@NonNull Network network, String apnName) { - UpdateNetworkWrapper updateNetworkWrapper = new UpdateNetworkWrapper(network, apnName); + public void updateNetwork(Network network, LinkProperties linkProperties) { + UpdateNetworkWrapper updateNetworkWrapper = + new UpdateNetworkWrapper(network, linkProperties); mHandler.sendMessage(mHandler.obtainMessage(EVENT_UPDATE_NETWORK, updateNetworkWrapper)); } /** @@ -694,7 +697,9 @@ public class EpdgTunnelManager { * @return true if params are valid and no existing tunnel. False otherwise. */ public boolean bringUpTunnel( - @NonNull TunnelSetupRequest setupRequest, @NonNull TunnelCallback tunnelCallback) { + @NonNull TunnelSetupRequest setupRequest, + @NonNull TunnelCallback tunnelCallback, + @NonNull TunnelMetricsInterface tunnelMetrics) { String apnName = setupRequest.apnName(); if (getTunnelSetupRequestApnName(setupRequest) == null) { @@ -719,7 +724,7 @@ public class EpdgTunnelManager { } TunnelRequestWrapper tunnelRequestWrapper = - new TunnelRequestWrapper(setupRequest, tunnelCallback); + new TunnelRequestWrapper(setupRequest, tunnelCallback, tunnelMetrics); mHandler.sendMessage( mHandler.obtainMessage(EVENT_TUNNEL_BRINGUP_REQUEST, tunnelRequestWrapper)); @@ -727,27 +732,33 @@ public class EpdgTunnelManager { return true; } - private void onBringUpTunnel(TunnelSetupRequest setupRequest, TunnelCallback tunnelCallback) { + private void onBringUpTunnel( + TunnelSetupRequest setupRequest, + TunnelCallback tunnelCallback, + TunnelMetricsInterface tunnelMetrics) { String apnName = setupRequest.apnName(); - IkeSessionParams ikeSessionParams = null; + IkeSessionParams ikeSessionParams; Log.d( TAG, "Bringing up tunnel for apn: " + apnName - + "ePDG : " + + " ePDG: " + mEpdgAddress.getHostAddress()); + final int token = incrementAndGetCurrentTokenForApn(apnName); + try { - ikeSessionParams = buildIkeSessionParams(setupRequest, apnName); + ikeSessionParams = buildIkeSessionParams(setupRequest, apnName, token); } catch (IwlanSimNotReadyException e) { - mRequestQueue.poll(); IwlanError iwlanError = new IwlanError(IwlanError.SIM_NOT_READY_EXCEPTION); reportIwlanError(apnName, iwlanError); tunnelCallback.onClosed(apnName, iwlanError); + tunnelMetrics.onClosed(new OnClosedMetrics.Builder().setApnName(apnName).build()); return; } + mIkeTunnelEstablishmentStartTime = System.currentTimeMillis(); IkeSession ikeSession = getIkeSessionCreator() .createIkeSession( @@ -755,14 +766,15 @@ public class EpdgTunnelManager { ikeSessionParams, buildChildSessionParams(setupRequest), Executors.newSingleThreadExecutor(), - getTmIkeSessionCallback(apnName), - new TmChildSessionCallback(apnName)); + getTmIkeSessionCallback(apnName, token), + new TmChildSessionCallback(apnName, token)); boolean isSrcIpv6Present = setupRequest.srcIpv6Address().isPresent(); putApnNameToTunnelConfig( apnName, ikeSession, tunnelCallback, + tunnelMetrics, isSrcIpv6Present ? setupRequest.srcIpv6Address().get() : null, setupRequest.srcIpv6AddressPrefixLength()); } @@ -795,9 +807,9 @@ public class EpdgTunnelManager { private ChildSessionParams buildChildSessionParams(TunnelSetupRequest setupRequest) { int proto = setupRequest.apnIpProtocol(); int hardTimeSeconds = - (int) getConfig(CarrierConfigManager.Iwlan.KEY_CHILD_SA_REKEY_HARD_TIMER_SEC_INT); + getConfig(CarrierConfigManager.Iwlan.KEY_CHILD_SA_REKEY_HARD_TIMER_SEC_INT); int softTimeSeconds = - (int) getConfig(CarrierConfigManager.Iwlan.KEY_CHILD_SA_REKEY_SOFT_TIMER_SEC_INT); + getConfig(CarrierConfigManager.Iwlan.KEY_CHILD_SA_REKEY_SOFT_TIMER_SEC_INT); if (!isValidChildSessionLifetime(hardTimeSeconds, softTimeSeconds)) { if (hardTimeSeconds > CHILD_HARD_LIFETIME_SEC_MAXIMUM && softTimeSeconds > CHILD_SOFT_LIFETIME_SEC_MINIMUM) { @@ -805,15 +817,11 @@ public class EpdgTunnelManager { softTimeSeconds = CHILD_HARD_LIFETIME_SEC_MAXIMUM - LIFETIME_MARGIN_SEC_MINIMUM; } else { hardTimeSeconds = - (int) - IwlanHelper.getDefaultConfig( - CarrierConfigManager.Iwlan - .KEY_CHILD_SA_REKEY_HARD_TIMER_SEC_INT); + IwlanHelper.getDefaultConfig( + CarrierConfigManager.Iwlan.KEY_CHILD_SA_REKEY_HARD_TIMER_SEC_INT); softTimeSeconds = - (int) - IwlanHelper.getDefaultConfig( - CarrierConfigManager.Iwlan - .KEY_CHILD_SA_REKEY_SOFT_TIMER_SEC_INT); + IwlanHelper.getDefaultConfig( + CarrierConfigManager.Iwlan.KEY_CHILD_SA_REKEY_SOFT_TIMER_SEC_INT); } Log.d( TAG, @@ -827,7 +835,7 @@ public class EpdgTunnelManager { new TunnelModeChildSessionParams.Builder() .setLifetimeSeconds(hardTimeSeconds, softTimeSeconds); - childSessionParamsBuilder.addSaProposal(buildChildSaProposal()); + childSessionParamsBuilder.addChildSaProposal(buildChildSaProposal()); boolean handoverIPv4Present = setupRequest.srcIpv4Address().isPresent(); boolean handoverIPv6Present = setupRequest.srcIpv6Address().isPresent(); @@ -898,7 +906,8 @@ public class EpdgTunnelManager { private @Nullable String getMobileDeviceIdentity() { TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class); telephonyManager = - telephonyManager.createForSubscriptionId(IwlanHelper.getSubId(mContext, mSlotId)); + Objects.requireNonNull(telephonyManager) + .createForSubscriptionId(IwlanHelper.getSubId(mContext, mSlotId)); if (telephonyManager == null) { return null; } @@ -917,12 +926,13 @@ public class EpdgTunnelManager { return imei.substring(0, imei.length() - 1) + imeisv_suffix; } - private IkeSessionParams buildIkeSessionParams(TunnelSetupRequest setupRequest, String apnName) + private IkeSessionParams buildIkeSessionParams( + TunnelSetupRequest setupRequest, String apnName, int token) throws IwlanSimNotReadyException { int hardTimeSeconds = - (int) getConfig(CarrierConfigManager.Iwlan.KEY_IKE_REKEY_HARD_TIMER_SEC_INT); + getConfig(CarrierConfigManager.Iwlan.KEY_IKE_REKEY_HARD_TIMER_SEC_INT); int softTimeSeconds = - (int) getConfig(CarrierConfigManager.Iwlan.KEY_IKE_REKEY_SOFT_TIMER_SEC_INT); + getConfig(CarrierConfigManager.Iwlan.KEY_IKE_REKEY_SOFT_TIMER_SEC_INT); if (!isValidIkeSessionLifetime(hardTimeSeconds, softTimeSeconds)) { if (hardTimeSeconds > IKE_HARD_LIFETIME_SEC_MAXIMUM && softTimeSeconds > IKE_SOFT_LIFETIME_SEC_MINIMUM) { @@ -930,15 +940,11 @@ public class EpdgTunnelManager { softTimeSeconds = IKE_HARD_LIFETIME_SEC_MAXIMUM - LIFETIME_MARGIN_SEC_MINIMUM; } else { hardTimeSeconds = - (int) - IwlanHelper.getDefaultConfig( - CarrierConfigManager.Iwlan - .KEY_IKE_REKEY_HARD_TIMER_SEC_INT); + IwlanHelper.getDefaultConfig( + CarrierConfigManager.Iwlan.KEY_IKE_REKEY_HARD_TIMER_SEC_INT); softTimeSeconds = - (int) - IwlanHelper.getDefaultConfig( - CarrierConfigManager.Iwlan - .KEY_IKE_REKEY_SOFT_TIMER_SEC_INT); + IwlanHelper.getDefaultConfig( + CarrierConfigManager.Iwlan.KEY_IKE_REKEY_SOFT_TIMER_SEC_INT); } Log.d( TAG, @@ -958,8 +964,8 @@ public class EpdgTunnelManager { .setLocalIdentification(getLocalIdentification()) .setRemoteIdentification(getId(setupRequest.apnName(), false)) .setAuthEap(null, getEapConfig()) - .addSaProposal(buildIkeSaProposal()) - .setNetwork(mNetwork) + .addIkeSaProposal(buildIkeSaProposal()) + .setNetwork(mDefaultNetwork) .addIkeOption(IkeSessionParams.IKE_OPTION_ACCEPT_ANY_REMOTE_ID) .addIkeOption(IkeSessionParams.IKE_OPTION_MOBIKE) .addIkeOption(IkeSessionParams.IKE_OPTION_REKEY_MOBILITY) @@ -987,39 +993,51 @@ public class EpdgTunnelManager { } } + // If MOBIKE is configured, ePDGs may force IPv6 UDP encapsulation- as specified by + // RFC 4555- which Android connectivity stack presently does not support. + if (mEpdgAddress instanceof Inet6Address) { + builder.removeIkeOption(IkeSessionParams.IKE_OPTION_MOBIKE); + } + Ike3gppParams.Builder builder3gppParams = null; - String imei = getMobileDeviceIdentity(); - if (imei != null) { - if (builder3gppParams == null) { - builder3gppParams = new Ike3gppParams.Builder(); + // TODO(b/239753287): Telus carrier requests DEVICE_IDENTITY, but errors out when parsing + // the response. Temporarily disabled. + if (false) { + String imei = getMobileDeviceIdentity(); + if (imei != null) { + if (builder3gppParams == null) { + builder3gppParams = new Ike3gppParams.Builder(); + } + Log.d(TAG, "DEVICE_IDENTITY set in Ike3gppParams"); + builder3gppParams.setMobileDeviceIdentity(imei); } - Log.d(TAG, "DEVICE_IDENTITY set in Ike3gppParams"); - builder3gppParams.setMobileDeviceIdentity(imei); } - if (setupRequest.pduSessionId() != 0) { - if (builder3gppParams == null) { + if (isN1ModeSupported()) { + if (setupRequest.pduSessionId() != 0) { + // Configures the PduSession ID in N1_MODE_CAPABILITY payload + // to notify the server that UE supports N1_MODE builder3gppParams = new Ike3gppParams.Builder(); + builder3gppParams.setPduSessionId((byte) setupRequest.pduSessionId()); } - builder3gppParams.setPduSessionId((byte) setupRequest.pduSessionId()); } if (builder3gppParams != null) { Ike3gppExtension extension = - new Ike3gppExtension(builder3gppParams.build(), new TmIke3gppCallback(apnName)); + new Ike3gppExtension( + builder3gppParams.build(), new TmIke3gppCallback(apnName, token)); builder.setIke3gppExtension(extension); } int nattKeepAliveTimer = - (int) getConfig(CarrierConfigManager.Iwlan.KEY_NATT_KEEP_ALIVE_TIMER_SEC_INT); + getConfig(CarrierConfigManager.Iwlan.KEY_NATT_KEEP_ALIVE_TIMER_SEC_INT); if (nattKeepAliveTimer < NATT_KEEPALIVE_DELAY_SEC_MIN || nattKeepAliveTimer > NATT_KEEPALIVE_DELAY_SEC_MAX) { Log.d(TAG, "Falling back to default natt keep alive timer"); nattKeepAliveTimer = - (int) - IwlanHelper.getDefaultConfig( - CarrierConfigManager.Iwlan.KEY_NATT_KEEP_ALIVE_TIMER_SEC_INT); + IwlanHelper.getDefaultConfig( + CarrierConfigManager.Iwlan.KEY_NATT_KEEP_ALIVE_TIMER_SEC_INT); } builder.setNattKeepAliveDelaySeconds(nattKeepAliveTimer); @@ -1027,23 +1045,17 @@ public class EpdgTunnelManager { } private boolean isValidChildSessionLifetime(int hardLifetimeSeconds, int softLifetimeSeconds) { - if (hardLifetimeSeconds < CHILD_HARD_LIFETIME_SEC_MINIMUM - || hardLifetimeSeconds > CHILD_HARD_LIFETIME_SEC_MAXIMUM - || softLifetimeSeconds < CHILD_SOFT_LIFETIME_SEC_MINIMUM - || hardLifetimeSeconds - softLifetimeSeconds < LIFETIME_MARGIN_SEC_MINIMUM) { - return false; - } - return true; + return hardLifetimeSeconds >= CHILD_HARD_LIFETIME_SEC_MINIMUM + && hardLifetimeSeconds <= CHILD_HARD_LIFETIME_SEC_MAXIMUM + && softLifetimeSeconds >= CHILD_SOFT_LIFETIME_SEC_MINIMUM + && hardLifetimeSeconds - softLifetimeSeconds >= LIFETIME_MARGIN_SEC_MINIMUM; } private boolean isValidIkeSessionLifetime(int hardLifetimeSeconds, int softLifetimeSeconds) { - if (hardLifetimeSeconds < IKE_HARD_LIFETIME_SEC_MINIMUM - || hardLifetimeSeconds > IKE_HARD_LIFETIME_SEC_MAXIMUM - || softLifetimeSeconds < IKE_SOFT_LIFETIME_SEC_MINIMUM - || hardLifetimeSeconds - softLifetimeSeconds < LIFETIME_MARGIN_SEC_MINIMUM) { - return false; - } - return true; + return hardLifetimeSeconds >= IKE_HARD_LIFETIME_SEC_MINIMUM + && hardLifetimeSeconds <= IKE_HARD_LIFETIME_SEC_MAXIMUM + && softLifetimeSeconds >= IKE_SOFT_LIFETIME_SEC_MINIMUM + && hardLifetimeSeconds - softLifetimeSeconds >= LIFETIME_MARGIN_SEC_MINIMUM; } private <T> T getConfig(String configKey) { @@ -1242,74 +1254,122 @@ public class EpdgTunnelManager { } private void onSessionClosedWithException( - IkeException exception, String apnName, int sessionType) { - IwlanError error = new IwlanError(exception); + IkeException exception, String apnName, int token, int sessionType) { Log.e( TAG, "Closing tunnel with exception for apn: " + apnName + + " with token: " + + token + " sessionType:" - + sessionType - + " error: " - + error); + + sessionType); exception.printStackTrace(); mHandler.sendMessage( - mHandler.obtainMessage(sessionType, new SessionClosedData(apnName, error))); + mHandler.obtainMessage( + sessionType, new SessionClosedData(apnName, token, exception))); + } + + private boolean isEpdgSelectionOrFirstTunnelBringUpInProgress() { + // Tunnel config is created but not connected to an ePDG. i.e., The first bring-up request + // in progress. + // No bring-up request in progress but pending queue is not empty. i.e. ePDG selection in + // progress + return (!mHasConnectedToEpdg && !mApnNameToTunnelConfig.isEmpty()) + || !mPendingBringUpRequests.isEmpty(); + } + + private IwlanError getErrorFromIkeException( + IkeException ikeException, IkeSessionState ikeSessionState) { + IwlanError error; + if (ikeException instanceof IkeIOException) { + error = new IwlanError(ikeSessionState.getErrorType(), ikeException); + } else { + error = new IwlanError(ikeException); + } + Log.e(TAG, "Closing tunnel: error: " + error + " state: " + ikeSessionState); + return error; } private final class TmHandler extends Handler { - private final String TAG = TmHandler.class.getSimpleName(); @Override public void handleMessage(Message msg) { - Log.d(TAG, "msg.what = " + msg.what); + Log.d(TAG, "msg.what = " + eventToString(msg.what)); String apnName; TunnelConfig tunnelConfig; + OnClosedMetrics.Builder onClosedMetricsBuilder; + switch (msg.what) { + case EVENT_CHILD_SESSION_OPENED: + case EVENT_IKE_SESSION_CLOSED: + case EVENT_IPSEC_TRANSFORM_CREATED: + case EVENT_IPSEC_TRANSFORM_DELETED: + case EVENT_CHILD_SESSION_CLOSED: + case EVENT_IKE_SESSION_OPENED: + case EVENT_IKE_SESSION_CONNECTION_INFO_CHANGED: + case EVENT_IKE_3GPP_DATA_RECEIVED: + IkeEventData ikeEventData = (IkeEventData) msg.obj; + if (isObsoleteToken(ikeEventData.mApnName, ikeEventData.mToken)) { + Log.d( + TAG, + eventToString(msg.what) + + " for obsolete token " + + ikeEventData.mToken); + return; + } + } + long mIkeTunnelEstablishmentDuration; switch (msg.what) { case EVENT_TUNNEL_BRINGUP_REQUEST: TunnelRequestWrapper tunnelRequestWrapper = (TunnelRequestWrapper) msg.obj; TunnelSetupRequest setupRequest = tunnelRequestWrapper.getSetupRequest(); + IwlanError bringUpError = null; + + onClosedMetricsBuilder = + new OnClosedMetrics.Builder().setApnName(setupRequest.apnName()); if (IwlanHelper.getSubId(mContext, mSlotId) == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { Log.e(TAG, "SIM isn't ready"); - IwlanError iwlanError = new IwlanError(IwlanError.SIM_NOT_READY_EXCEPTION); - reportIwlanError(setupRequest.apnName(), iwlanError); - tunnelRequestWrapper - .getTunnelCallback() - .onClosed(setupRequest.apnName(), iwlanError); - return; + bringUpError = new IwlanError(IwlanError.SIM_NOT_READY_EXCEPTION); + reportIwlanError(setupRequest.apnName(), bringUpError); + } else if (Objects.isNull(mDefaultNetwork)) { + Log.e(TAG, "The default network is not ready"); + bringUpError = new IwlanError(IwlanError.IKE_INTERNAL_IO_EXCEPTION); + reportIwlanError(setupRequest.apnName(), bringUpError); + } else if (!canBringUpTunnel(setupRequest.apnName())) { + Log.d(TAG, "Cannot bring up tunnel as retry time has not passed"); + bringUpError = getLastError(setupRequest.apnName()); } - if (!canBringUpTunnel(setupRequest.apnName())) { - Log.d(TAG, "Cannot bring up tunnel as retry time has not passed"); + if (Objects.nonNull(bringUpError)) { tunnelRequestWrapper .getTunnelCallback() - .onClosed( - setupRequest.apnName(), - getLastError(setupRequest.apnName())); + .onClosed(setupRequest.apnName(), bringUpError); + tunnelRequestWrapper + .getTunnelMetrics() + .onClosed(onClosedMetricsBuilder.build()); return; } - // No tunnel bring up in progress and the epdg address is null - if (!mIsEpdgAddressSelected - && mApnNameToTunnelConfig.size() == 0 - && mRequestQueue.size() == 0) { - mNetwork = setupRequest.network(); - mRequestQueue.add(tunnelRequestWrapper); - selectEpdgAddress(setupRequest); + if (mHasConnectedToEpdg) { + // Service the request immediately when epdg address is available + onBringUpTunnel( + setupRequest, + tunnelRequestWrapper.getTunnelCallback(), + tunnelRequestWrapper.getTunnelMetrics()); break; } - // Service the request immediately when epdg address is available - if (mIsEpdgAddressSelected) { - onBringUpTunnel(setupRequest, tunnelRequestWrapper.getTunnelCallback()); - } else { - mRequestQueue.add(tunnelRequestWrapper); + if (!isEpdgSelectionOrFirstTunnelBringUpInProgress()) { + // No tunnel bring-up in progress. Select the ePDG address first + selectEpdgAddress(setupRequest); } + + // Another bring-up or ePDG selection is in progress, pending this request. + mPendingBringUpRequests.add(tunnelRequestWrapper); break; case EVENT_EPDG_ADDRESS_SELECTION_REQUEST_COMPLETE: @@ -1321,17 +1381,19 @@ public class EpdgTunnelManager { break; } - if ((tunnelRequestWrapper = mRequestQueue.peek()) == null) { + if (mPendingBringUpRequests.isEmpty()) { Log.d(TAG, "Empty request queue"); break; } if (selectorResult.getEpdgError().getErrorType() == IwlanError.NO_ERROR && selectorResult.getValidIpList() != null) { + tunnelRequestWrapper = mPendingBringUpRequests.remove(); validateAndSetEpdgAddress(selectorResult.getValidIpList()); onBringUpTunnel( tunnelRequestWrapper.getSetupRequest(), - tunnelRequestWrapper.getTunnelCallback()); + tunnelRequestWrapper.getTunnelCallback(), + tunnelRequestWrapper.getTunnelMetrics()); } else { IwlanError error = (selectorResult.getEpdgError().getErrorType() @@ -1372,11 +1434,28 @@ public class EpdgTunnelManager { .build(); tunnelConfig.getTunnelCallback().onOpened(apnName, linkProperties); - setIsEpdgAddressSelected(true); + reportIwlanError(apnName, new IwlanError(IwlanError.NO_ERROR)); + + mIkeTunnelEstablishmentDuration = + System.currentTimeMillis() - mIkeTunnelEstablishmentStartTime; + mIkeTunnelEstablishmentStartTime = 0; + tunnelConfig + .getTunnelMetrics() + .onOpened( + new OnOpenedMetrics.Builder() + .setApnName(apnName) + .setEpdgServerAddress(mEpdgAddress) + .setEpdgServerSelectionDuration( + (int) mEpdgServerSelectionDuration) + .setIkeTunnelEstablishmentDuration( + (int) mIkeTunnelEstablishmentDuration) + .build()); + + onConnectedToEpdg(true); mValidEpdgInfo.resetIndex(); - mRequestQueue.poll(); printRequestQueue("EVENT_CHILD_SESSION_OPENED"); serviceAllPendingRequests(); + tunnelConfig.setIkeSessionState(IkeSessionState.CHILD_SESSION_OPENED); break; case EVENT_IKE_SESSION_CLOSED: @@ -1392,17 +1471,23 @@ public class EpdgTunnelManager { // If IKE session closed exceptionally, we retrieve IwlanError directly from the // exception; otherwise, it is still possible that we triggered an IKE session - // close due to an error (eg. IwlanError.TUNNEL_TRANSFORM_FAILED), or because + // close due to an error (e.g. IwlanError.TUNNEL_TRANSFORM_FAILED), or because // the Child session closed exceptionally; in which case, we attempt to retrieve // the stored error (if any) from TunnelConfig. IwlanError iwlanError; - if (sessionClosedData.mIwlanError.getErrorType() != IwlanError.NO_ERROR) { - iwlanError = sessionClosedData.mIwlanError; + if (sessionClosedData.mIkeException != null) { + iwlanError = + getErrorFromIkeException( + sessionClosedData.mIkeException, + tunnelConfig.getIkeSessionState()); } else { - // If IKE session setup failed without error cause, Iwlan reports - // NETWORK_FAILURE instead of NO_ERROR + // If IKE session opened, then closed before child session (and IWLAN + // tunnel) opened. + // Iwlan reports IKE_SESSION_CLOSED_BEFORE_CHILD_SESSION_OPENED + // instead of NO_ERROR if (!tunnelConfig.hasTunnelOpened()) { - iwlanError = new IwlanError(IwlanError.NETWORK_FAILURE); + iwlanError = new IwlanError( + IwlanError.IKE_SESSION_CLOSED_BEFORE_CHILD_SESSION_OPENED); } else { iwlanError = tunnelConfig.getError(); } @@ -1413,65 +1498,105 @@ public class EpdgTunnelManager { iface.close(); } - if (!mIsEpdgAddressSelected) { - // fail all the requests. report back off timer, if present, to the - // current request. - if (tunnelConfig.isBackoffTimeValid()) { - mRequestQueue.poll(); - reportIwlanError(apnName, iwlanError, tunnelConfig.getBackoffTime()); - tunnelConfig.getTunnelCallback().onClosed(apnName, iwlanError); - } - failAllPendingRequests(iwlanError); - } else { - mRequestQueue.poll(); - Log.d(TAG, "Tunnel Closed: " + iwlanError); + if (!tunnelConfig.hasTunnelOpened()) { if (tunnelConfig.isBackoffTimeValid()) { reportIwlanError(apnName, iwlanError, tunnelConfig.getBackoffTime()); } else { reportIwlanError(apnName, iwlanError); } - tunnelConfig.getTunnelCallback().onClosed(apnName, iwlanError); + } + + Log.d(TAG, "Tunnel Closed: " + iwlanError); + tunnelConfig.setIkeSessionState(IkeSessionState.NO_IKE_SESSION); + tunnelConfig.getTunnelCallback().onClosed(apnName, iwlanError); + onClosedMetricsBuilder = new OnClosedMetrics.Builder().setApnName(apnName); + + if (!mHasConnectedToEpdg) { + failAllPendingRequests(iwlanError); + tunnelConfig.getTunnelMetrics().onClosed(onClosedMetricsBuilder.build()); + } else { + mIkeTunnelEstablishmentDuration = + mIkeTunnelEstablishmentStartTime > 0 + ? System.currentTimeMillis() + - mIkeTunnelEstablishmentStartTime + : 0; + mIkeTunnelEstablishmentStartTime = 0; + + onClosedMetricsBuilder + .setEpdgServerAddress(mEpdgAddress) + .setEpdgServerSelectionDuration((int) mEpdgServerSelectionDuration) + .setIkeTunnelEstablishmentDuration( + (int) mIkeTunnelEstablishmentDuration); + tunnelConfig.getTunnelMetrics().onClosed(onClosedMetricsBuilder.build()); } mApnNameToTunnelConfig.remove(apnName); - if (mApnNameToTunnelConfig.size() == 0 && mRequestQueue.size() == 0) { - resetTunnelManagerState(); + if (mApnNameToTunnelConfig.size() == 0 && mPendingBringUpRequests.isEmpty()) { + onConnectedToEpdg(false); } + break; case EVENT_UPDATE_NETWORK: UpdateNetworkWrapper updatedNetwork = (UpdateNetworkWrapper) msg.obj; - apnName = updatedNetwork.getApnName(); - Network network = updatedNetwork.getNetwork(); - tunnelConfig = mApnNameToTunnelConfig.get(apnName); - - // Update the global cache if they aren't equal - if (mNetwork == null || !mNetwork.equals(network)) { - Log.d(TAG, "Updating mNetwork to " + network); - mNetwork = network; - } - - if (tunnelConfig == null) { - Log.d(TAG, "Update Network request: No tunnel exists for apn: " + apnName); - } else { - Log.d(TAG, "Updating Network for apn: " + apnName + " Network: " + network); - tunnelConfig.getIkeSession().setNetwork(network); + mDefaultNetwork = updatedNetwork.getNetwork(); + LinkProperties defaultLinkProperties = updatedNetwork.getLinkProperties(); + String paraString = "Network: " + mDefaultNetwork; + + if (mHasConnectedToEpdg) { + if (Objects.isNull(mDefaultNetwork)) { + Log.w(TAG, "The default network has been removed."); + } else if (Objects.isNull(defaultLinkProperties)) { + Log.w( + TAG, + "The default network's LinkProperties is not ready ." + + paraString); + } else if (!defaultLinkProperties.isReachable(mEpdgAddress)) { + Log.w( + TAG, + "The default network link " + + defaultLinkProperties + + " doesn't have a route to the ePDG " + + mEpdgAddress + + paraString); + } else if (Objects.equals(mDefaultNetwork, mIkeSessionNetwork)) { + Log.w( + TAG, + "The default network has not changed from the IKE session" + + " network. " + + paraString); + } else { + mApnNameToTunnelConfig.forEach( + (apn, config) -> { + Log.d( + TAG, + "The Underlying Network is updating for APN (+" + + apn + + "). " + + paraString); + config.getIkeSession().setNetwork(mDefaultNetwork); + config.setIkeSessionState( + IkeSessionState.IKE_MOBILITY_IN_PROGRESS); + }); + mIkeSessionNetwork = mDefaultNetwork; + } } break; case EVENT_TUNNEL_BRINGDOWN_REQUEST: - apnName = (String) msg.obj; - int forceClose = msg.arg1; + TunnelBringdownRequest bringdownRequest = (TunnelBringdownRequest) msg.obj; + apnName = bringdownRequest.mApnName; + boolean forceClose = bringdownRequest.mForceClose; tunnelConfig = mApnNameToTunnelConfig.get(apnName); if (tunnelConfig == null) { - Log.d( + Log.w( TAG, "Bringdown request: No tunnel exists for apn: " + apnName + "forced: " + forceClose); } else { - if (forceClose == 1) { + if (forceClose) { tunnelConfig.getIkeSession().kill(); } else { tunnelConfig.getIkeSession().close(); @@ -1481,6 +1606,15 @@ public class EpdgTunnelManager { if (numClosed > 0) { Log.d(TAG, "Closed " + numClosed + " pending requests for apn: " + apnName); } + if (tunnelConfig == null && numClosed == 0) { + // IwlanDataService expected to close a (pending or up) tunnel but was not + // found. Recovers state in IwlanDataService through TunnelCallback. + iwlanError = new IwlanError(IwlanError.TUNNEL_NOT_FOUND); + reportIwlanError(apnName, iwlanError); + bringdownRequest.mTunnelCallback.onClosed(apnName, iwlanError); + bringdownRequest.mIwlanTunnelMetrics.onClosed( + new OnClosedMetrics.Builder().setApnName(apnName).build()); + } break; case EVENT_IPSEC_TRANSFORM_CREATED: @@ -1490,30 +1624,12 @@ public class EpdgTunnelManager { tunnelConfig = mApnNameToTunnelConfig.get(apnName); if (tunnelConfig.getIface() == null) { - if (mLocalAddresses == null - || mLocalAddresses.size() == 0 - || ipSecManager == null) { - Log.e(TAG, "No local addresses found."); - closeIkeSession( - apnName, new IwlanError(IwlanError.TUNNEL_TRANSFORM_FAILED)); - return; - } - try { - if (mEpdgAddress instanceof Inet4Address - && mProtoFilter == EpdgSelector.PROTO_FILTER_IPV6) { - mLocalAddresses = - IwlanHelper.getStackedAddressesForNetwork( - mNetwork, mContext); - } - InetAddress localAddress = - (mEpdgAddress instanceof Inet4Address) - ? IwlanHelper.getIpv4Address(mLocalAddresses) - : IwlanHelper.getIpv6Address(mLocalAddresses); - Log.d(TAG, "Local address = " + localAddress); tunnelConfig.setIface( ipSecManager.createIpSecTunnelInterface( - localAddress, mEpdgAddress, mNetwork)); + DUMMY_ADDR /* unused */, + DUMMY_ADDR /* unused */, + mDefaultNetwork)); } catch (IpSecManager.ResourceUnavailableException | IOException e) { Log.e(TAG, "Failed to create tunnel interface. " + e); closeIkeSession( @@ -1523,6 +1639,7 @@ public class EpdgTunnelManager { } try { + assert ipSecManager != null; ipSecManager.applyTunnelModeTransform( tunnelConfig.getIface(), transformData.getDirection(), @@ -1534,10 +1651,15 @@ public class EpdgTunnelManager { closeIkeSession( apnName, new IwlanError(IwlanError.TUNNEL_TRANSFORM_FAILED)); } + if (tunnelConfig.getIkeSessionState() + == IkeSessionState.IKE_MOBILITY_IN_PROGRESS) { + tunnelConfig.setIkeSessionState(IkeSessionState.CHILD_SESSION_OPENED); + } break; case EVENT_IPSEC_TRANSFORM_DELETED: - IpSecTransform transform = (IpSecTransform) msg.obj; + transformData = (IpsecTransformData) msg.obj; + IpSecTransform transform = transformData.getTransform(); transform.close(); break; @@ -1550,23 +1672,28 @@ public class EpdgTunnelManager { Log.d(TAG, "No tunnel callback for apn: " + apnName); return; } - tunnelConfig.setError(sessionClosedData.mIwlanError); + if (sessionClosedData.mIkeException != null) { + tunnelConfig.setError( + getErrorFromIkeException( + sessionClosedData.mIkeException, + tunnelConfig.getIkeSessionState())); + } tunnelConfig.getIkeSession().close(); break; case EVENT_IKE_SESSION_OPENED: IkeSessionOpenedData ikeSessionOpenedData = (IkeSessionOpenedData) msg.obj; + apnName = ikeSessionOpenedData.mApnName; IkeSessionConfiguration sessionConfiguration = ikeSessionOpenedData.mIkeSessionConfiguration; - tunnelConfig = mApnNameToTunnelConfig.get(ikeSessionOpenedData.mApnName); + tunnelConfig = mApnNameToTunnelConfig.get(apnName); tunnelConfig.setPcscfAddrList(sessionConfiguration.getPcscfServers()); boolean enabledFastReauth = - (boolean) - getConfig( - CarrierConfigManager.Iwlan - .KEY_SUPPORTS_EAP_AKA_FAST_REAUTH_BOOL); + getConfig( + CarrierConfigManager.Iwlan + .KEY_SUPPORTS_EAP_AKA_FAST_REAUTH_BOOL); Log.d( TAG, "CarrierConfigManager.Iwlan.KEY_SUPPORTS_EAP_AKA_FAST_REAUTH_BOOL " @@ -1574,7 +1701,7 @@ public class EpdgTunnelManager { if (enabledFastReauth) { EapInfo eapInfo = sessionConfiguration.getEapInfo(); - if (eapInfo != null && eapInfo instanceof EapAkaInfo) { + if (eapInfo instanceof EapAkaInfo) { mNextReauthId = ((EapAkaInfo) eapInfo).getReauthId(); Log.d(TAG, "Update ReauthId: " + Arrays.toString(mNextReauthId)); } else { @@ -1586,12 +1713,14 @@ public class EpdgTunnelManager { case EVENT_IKE_SESSION_CONNECTION_INFO_CHANGED: IkeSessionConnectionInfoData ikeSessionConnectionInfoData = (IkeSessionConnectionInfoData) msg.obj; - network = ikeSessionConnectionInfoData.mIkeSessionConnectionInfo.getNetwork(); + Network network = + ikeSessionConnectionInfoData.mIkeSessionConnectionInfo.getNetwork(); apnName = ikeSessionConnectionInfoData.mApnName; ConnectivityManager connectivityManager = mContext.getSystemService(ConnectivityManager.class); - if (connectivityManager.getLinkProperties(network) == null) { + if (Objects.requireNonNull(connectivityManager).getLinkProperties(network) + == null) { Log.e(TAG, "Network " + network + " has null LinkProperties!"); return; } @@ -1610,6 +1739,38 @@ public class EpdgTunnelManager { } break; + case EVENT_IKE_3GPP_DATA_RECEIVED: + Ike3gppDataReceived ike3gppDataReceived = (Ike3gppDataReceived) msg.obj; + apnName = ike3gppDataReceived.mApnName; + List<Ike3gppData> ike3gppData = ike3gppDataReceived.mIke3gppData; + if (ike3gppData != null && !ike3gppData.isEmpty()) { + tunnelConfig = mApnNameToTunnelConfig.get(apnName); + for (Ike3gppData payload : ike3gppData) { + if (payload.getDataType() == DATA_TYPE_NOTIFY_N1_MODE_INFORMATION) { + Log.d(TAG, "Got payload DATA_TYPE_NOTIFY_N1_MODE_INFORMATION"); + NetworkSliceInfo si = + NetworkSliceSelectionAssistanceInformation.getSliceInfo( + ((Ike3gppN1ModeInformation) payload).getSnssai()); + if (si != null) { + tunnelConfig.setSliceInfo(si); + Log.d(TAG, "SliceInfo: " + si); + } + } else if (payload.getDataType() == DATA_TYPE_NOTIFY_BACKOFF_TIMER) { + Log.d(TAG, "Got payload DATA_TYPE_NOTIFY_BACKOFF_TIMER"); + long backoffTime = + decodeBackoffTime( + ((Ike3gppBackoffTimer) payload).getBackoffTimer()); + if (backoffTime > 0) { + tunnelConfig.setBackoffTime(backoffTime); + Log.d(TAG, "Backoff Timer: " + backoffTime); + } + } + } + } else { + Log.e(TAG, "Null or empty payloads received:"); + } + break; + default: throw new IllegalStateException("Unexpected value: " + msg.what); } @@ -1627,35 +1788,58 @@ public class EpdgTunnelManager { } private void selectEpdgAddress(TunnelSetupRequest setupRequest) { - mLocalAddresses = getAddressForNetwork(mNetwork, mContext); - if (mLocalAddresses == null || mLocalAddresses.size() == 0) { - Log.e(TAG, "No local addresses available."); - failAllPendingRequests( - new IwlanError(IwlanError.EPDG_SELECTOR_SERVER_SELECTION_FAILED)); + ++mTransactionId; + mEpdgServerSelectionStartTime = System.currentTimeMillis(); + + final int ipPreference = + IwlanHelper.getConfig( + CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_IP_TYPE_PREFERENCE_INT, + mContext, + mSlotId); + + IpPreferenceConflict ipPreferenceConflict = + isIpPreferenceConflictsWithNetwork(ipPreference); + if (ipPreferenceConflict.mIsConflict) { + sendSelectionRequestComplete( + null, new IwlanError(ipPreferenceConflict.mErrorType), mTransactionId); return; } - mProtoFilter = EpdgSelector.PROTO_FILTER_IPV4V6; - if (!IwlanHelper.hasIpv6Address(mLocalAddresses)) { - mProtoFilter = EpdgSelector.PROTO_FILTER_IPV4; - } - if (!IwlanHelper.hasIpv4Address(mLocalAddresses)) { - mProtoFilter = EpdgSelector.PROTO_FILTER_IPV6; + int protoFilter = EpdgSelector.PROTO_FILTER_IPV4V6; + int epdgAddressOrder = EpdgSelector.SYSTEM_PREFERRED; + switch (ipPreference) { + case CarrierConfigManager.Iwlan.EPDG_ADDRESS_IPV4_PREFERRED: + epdgAddressOrder = EpdgSelector.IPV4_PREFERRED; + break; + case CarrierConfigManager.Iwlan.EPDG_ADDRESS_IPV6_PREFERRED: + epdgAddressOrder = EpdgSelector.IPV6_PREFERRED; + break; + case CarrierConfigManager.Iwlan.EPDG_ADDRESS_IPV4_ONLY: + protoFilter = EpdgSelector.PROTO_FILTER_IPV4; + break; + case CarrierConfigManager.Iwlan.EPDG_ADDRESS_IPV6_ONLY: + protoFilter = EpdgSelector.PROTO_FILTER_IPV6; + break; + case CarrierConfigManager.Iwlan.EPDG_ADDRESS_SYSTEM_PREFERRED: + break; + default: + Log.w(TAG, "Invalid Ip preference : " + ipPreference); } EpdgSelector epdgSelector = getEpdgSelector(); IwlanError epdgError = epdgSelector.getValidatedServerList( - ++mTransactionId, - mProtoFilter, + mTransactionId, + protoFilter, + epdgAddressOrder, setupRequest.isRoaming(), setupRequest.isEmergency(), - mNetwork, + mDefaultNetwork, mSelectorCallback); if (epdgError.getErrorType() != IwlanError.NO_ERROR) { Log.e(TAG, "Epdg address selection failed with error:" + epdgError); - failAllPendingRequests(epdgError); + sendSelectionRequestComplete(null, epdgError, mTransactionId); } } @@ -1667,24 +1851,29 @@ public class EpdgTunnelManager { @VisibleForTesting int closePendingRequestsForApn(String apnName) { int numRequestsClosed = 0; - int queueSize = mRequestQueue.size(); + int queueSize = mPendingBringUpRequests.size(); if (queueSize == 0) { return numRequestsClosed; } - int count = 0; - - while (count < queueSize) { - TunnelRequestWrapper requestWrapper = mRequestQueue.poll(); - if (requestWrapper.getSetupRequest().apnName() == apnName) { + for (int count = 0; count < queueSize; count++) { + TunnelRequestWrapper requestWrapper = mPendingBringUpRequests.remove(); + if (requestWrapper.getSetupRequest().apnName().equals(apnName)) { requestWrapper .getTunnelCallback() .onClosed(apnName, new IwlanError(IwlanError.NO_ERROR)); + + requestWrapper + .getTunnelMetrics() + .onClosed( + new OnClosedMetrics.Builder() + .setApnName(apnName) + .setEpdgServerAddress(mEpdgAddress) + .build()); numRequestsClosed++; } else { - mRequestQueue.add(requestWrapper); + mPendingBringUpRequests.add(requestWrapper); } - count++; } return numRequestsClosed; } @@ -1709,69 +1898,74 @@ public class EpdgTunnelManager { mValidEpdgInfo.incrementIndex(); } - @VisibleForTesting - void resetTunnelManagerState() { - Log.d(TAG, "resetTunnelManagerState"); - mEpdgAddress = null; - setIsEpdgAddressSelected(false); - mNetwork = null; - mRequestQueue = new LinkedList<>(); - mApnNameToTunnelConfig = new ConcurrentHashMap<>(); - mLocalAddresses = null; - } - private void serviceAllPendingRequests() { - while (mRequestQueue.size() > 0) { + while (!mPendingBringUpRequests.isEmpty()) { Log.d(TAG, "serviceAllPendingRequests"); - TunnelRequestWrapper request = mRequestQueue.poll(); - onBringUpTunnel(request.getSetupRequest(), request.getTunnelCallback()); + TunnelRequestWrapper request = mPendingBringUpRequests.remove(); + onBringUpTunnel( + request.getSetupRequest(), + request.getTunnelCallback(), + request.getTunnelMetrics()); } } private void failAllPendingRequests(IwlanError error) { - while (mRequestQueue.size() > 0) { + while (!mPendingBringUpRequests.isEmpty()) { Log.d(TAG, "failAllPendingRequests"); - TunnelRequestWrapper request = mRequestQueue.poll(); + TunnelRequestWrapper request = mPendingBringUpRequests.remove(); TunnelSetupRequest setupRequest = request.getSetupRequest(); reportIwlanError(setupRequest.apnName(), error); request.getTunnelCallback().onClosed(setupRequest.apnName(), error); + request.getTunnelMetrics() + .onClosed( + new OnClosedMetrics.Builder() + .setApnName(setupRequest.apnName()) + .setEpdgServerAddress(mEpdgAddress) + .build()); } } - // Prints mRequestQueue + // Prints mPendingBringUpRequests private void printRequestQueue(String info) { Log.d(TAG, info); - Log.d(TAG, "mRequestQueue: " + Arrays.toString(mRequestQueue.toArray())); + Log.d( + TAG, + "mPendingBringUpRequests: " + Arrays.toString(mPendingBringUpRequests.toArray())); } // Update Network wrapper private static final class UpdateNetworkWrapper { private final Network mNetwork; - private final String mApnName; + private final LinkProperties mLinkProperties; - private UpdateNetworkWrapper(Network network, String apnName) { + private UpdateNetworkWrapper(Network network, LinkProperties linkProperties) { mNetwork = network; - mApnName = apnName; - } - - public String getApnName() { - return mApnName; + mLinkProperties = linkProperties; } public Network getNetwork() { return mNetwork; } + + public LinkProperties getLinkProperties() { + return mLinkProperties; + } } + // Tunnel request + tunnel callback private static final class TunnelRequestWrapper { private final TunnelSetupRequest mSetupRequest; private final TunnelCallback mTunnelCallback; + private final TunnelMetricsInterface mTunnelMetrics; private TunnelRequestWrapper( - TunnelSetupRequest setupRequest, TunnelCallback tunnelCallback) { + TunnelSetupRequest setupRequest, + TunnelCallback tunnelCallback, + TunnelMetricsInterface tunnelMetrics) { mTunnelCallback = tunnelCallback; mSetupRequest = setupRequest; + mTunnelMetrics = tunnelMetrics; } public TunnelSetupRequest getSetupRequest() { @@ -1781,6 +1975,28 @@ public class EpdgTunnelManager { public TunnelCallback getTunnelCallback() { return mTunnelCallback; } + + public TunnelMetricsInterface getTunnelMetrics() { + return mTunnelMetrics; + } + } + + private static final class TunnelBringdownRequest { + final String mApnName; + final boolean mForceClose; + final TunnelCallback mTunnelCallback; + final IwlanTunnelMetricsImpl mIwlanTunnelMetrics; + + private TunnelBringdownRequest( + String apnName, + boolean forceClose, + TunnelCallback tunnelCallback, + IwlanTunnelMetricsImpl iwlanTunnelMetrics) { + mApnName = apnName; + mForceClose = forceClose; + mTunnelCallback = tunnelCallback; + mIwlanTunnelMetrics = iwlanTunnelMetrics; + } } private static final class EpdgSelectorResult { @@ -1810,70 +2026,71 @@ public class EpdgTunnelManager { } // Data received from IkeSessionStateMachine on successful EVENT_CHILD_SESSION_OPENED. - private static final class TunnelOpenedData { - final String mApnName; + private static final class TunnelOpenedData extends IkeEventData { final List<InetAddress> mInternalDnsServers; final List<LinkAddress> mInternalAddresses; private TunnelOpenedData( String apnName, + int token, List<InetAddress> internalDnsServers, List<LinkAddress> internalAddresses) { - mApnName = apnName; + super(apnName, token); mInternalDnsServers = internalDnsServers; mInternalAddresses = internalAddresses; } } // Data received from IkeSessionStateMachine on successful EVENT_IKE_SESSION_OPENED. - private static final class IkeSessionOpenedData { - final String mApnName; + private static final class IkeSessionOpenedData extends IkeEventData { final IkeSessionConfiguration mIkeSessionConfiguration; private IkeSessionOpenedData( - String apnName, IkeSessionConfiguration ikeSessionConfiguration) { - mApnName = apnName; + String apnName, int token, IkeSessionConfiguration ikeSessionConfiguration) { + super(apnName, token); mIkeSessionConfiguration = ikeSessionConfiguration; } } - private static final class IkeSessionConnectionInfoData { - final String mApnName; + private static final class IkeSessionConnectionInfoData extends IkeEventData { final IkeSessionConnectionInfo mIkeSessionConnectionInfo; private IkeSessionConnectionInfoData( - String apnName, IkeSessionConnectionInfo ikeSessionConnectionInfo) { - mApnName = apnName; + String apnName, int token, IkeSessionConnectionInfo ikeSessionConnectionInfo) { + super(apnName, token); mIkeSessionConnectionInfo = ikeSessionConnectionInfo; } } - // Data received from IkeSessionStateMachine if either IKE session or Child session have been - // closed, normally or exceptionally. - private static final class SessionClosedData { - final String mApnName; - final IwlanError mIwlanError; + private static final class Ike3gppDataReceived extends IkeEventData { + final List<Ike3gppData> mIke3gppData; - private SessionClosedData(String apnName, IwlanError iwlanError) { - mApnName = apnName; - mIwlanError = iwlanError; + private Ike3gppDataReceived(String apnName, int token, List<Ike3gppData> ike3gppData) { + super(apnName, token); + mIke3gppData = ike3gppData; } } - public void releaseInstance() { - mHandlerThread.quit(); - mTunnelManagerInstances.remove(mSlotId); + // Data received from IkeSessionStateMachine if either IKE session or Child session have been + // closed, normally or exceptionally. + private static final class SessionClosedData extends IkeEventData { + final IkeException mIkeException; + + private SessionClosedData(String apnName, int token, IkeException ikeException) { + super(apnName, token); + mIkeException = ikeException; + } } - private static final class IpsecTransformData { + private static final class IpsecTransformData extends IkeEventData { private final IpSecTransform mTransform; private final int mDirection; - private final String mApnName; - private IpsecTransformData(IpSecTransform transform, int direction, String apnName) { + private IpsecTransformData( + IpSecTransform transform, int direction, String apnName, int token) { + super(apnName, token); mTransform = transform; mDirection = direction; - mApnName = apnName; } public IpSecTransform getTransform() { @@ -1885,7 +2102,17 @@ public class EpdgTunnelManager { } public String getApnName() { - return mApnName; + return super.mApnName; + } + } + + private abstract static class IkeEventData { + final String mApnName; + final int mToken; + + private IkeEventData(String apnName, int token) { + mApnName = apnName; + mToken = token; } } @@ -1924,16 +2151,28 @@ public class EpdgTunnelManager { } } + private static class IpPreferenceConflict { + final boolean mIsConflict; + final int mErrorType; + + private IpPreferenceConflict(boolean isConflict, int errorType) { + mIsConflict = isConflict; + mErrorType = errorType; + } + + private IpPreferenceConflict() { + mIsConflict = false; + mErrorType = IwlanError.NO_ERROR; + } + } + private int[] getRetransmissionTimeoutsFromConfig() { - int[] timeList = - (int[]) getConfig(CarrierConfigManager.Iwlan.KEY_RETRANSMIT_TIMER_MSEC_INT_ARRAY); - boolean isValid = true; - if (timeList == null - || timeList.length == 0 - || timeList.length > IKE_RETRANS_MAX_ATTEMPTS_MAX) { - isValid = false; - } - for (int time : timeList) { + int[] timeList = getConfig(CarrierConfigManager.Iwlan.KEY_RETRANSMIT_TIMER_MSEC_INT_ARRAY); + boolean isValid = + timeList != null + && timeList.length != 0 + && timeList.length <= IKE_RETRANS_MAX_ATTEMPTS_MAX; + for (int time : Objects.requireNonNull(timeList)) { if (time < IKE_RETRANS_TIMEOUT_MS_MIN || time > IKE_RETRANS_TIMEOUT_MS_MAX) { isValid = false; break; @@ -1941,21 +2180,18 @@ public class EpdgTunnelManager { } if (!isValid) { timeList = - (int[]) - IwlanHelper.getDefaultConfig( - CarrierConfigManager.Iwlan.KEY_RETRANSMIT_TIMER_MSEC_INT_ARRAY); + IwlanHelper.getDefaultConfig( + CarrierConfigManager.Iwlan.KEY_RETRANSMIT_TIMER_MSEC_INT_ARRAY); } Log.d(TAG, "getRetransmissionTimeoutsFromConfig: " + Arrays.toString(timeList)); return timeList; } private int getDpdDelayFromConfig() { - int dpdDelay = (int) getConfig(CarrierConfigManager.Iwlan.KEY_DPD_TIMER_SEC_INT); + int dpdDelay = getConfig(CarrierConfigManager.Iwlan.KEY_DPD_TIMER_SEC_INT); if (dpdDelay < IKE_DPD_DELAY_SEC_MIN || dpdDelay > IKE_DPD_DELAY_SEC_MAX) { dpdDelay = - (int) - IwlanHelper.getDefaultConfig( - CarrierConfigManager.Iwlan.KEY_DPD_TIMER_SEC_INT); + IwlanHelper.getDefaultConfig(CarrierConfigManager.Iwlan.KEY_DPD_TIMER_SEC_INT); } return dpdDelay; } @@ -1971,7 +2207,7 @@ public class EpdgTunnelManager { * incremented in multiples of 1 minute 1 1 0 value is incremented in multiples of 1 hour 1 1 1 * value indicates that the timer is deactivated. * - * @param backoffTimerByte Byte value obtained from ike + * @param backoffTimeByte Byte value obtained from ike * @return long time value in seconds. -1 if the timer needs to be deactivated. */ private static long decodeBackoffTime(byte backoffTimeByte) { @@ -1979,12 +2215,12 @@ public class EpdgTunnelManager { final int BACKOFF_TIMER_UNIT_MASK = 0xE0; final Long[] BACKOFF_TIMER_UNIT_INCREMENT_SECS = { 10L * 60L, // 10 mins - 1L * 60L * 60L, // 1 hour + 60L * 60L, // 1 hour 10L * 60L * 60L, // 10 hours 2L, // 2 seconds 30L, // 30 seconds - 1L * 60L, // 1 minute - 1L * 60L * 60L, // 1 hour + 60L, // 1 minute + 60L * 60L, // 1 hour }; long time = backoffTimeByte & BACKOFF_TIME_VALUE_MASK; @@ -1998,8 +2234,7 @@ public class EpdgTunnelManager { @VisibleForTesting String getTunnelSetupRequestApnName(TunnelSetupRequest setupRequest) { - String apnName = setupRequest.apnName(); - return apnName; + return setupRequest.apnName(); } @VisibleForTesting @@ -2007,29 +2242,48 @@ public class EpdgTunnelManager { String apnName, IkeSession ikeSession, TunnelCallback tunnelCallback, + TunnelMetricsInterface tunnelMetrics, InetAddress srcIpv6Addr, int srcIPv6AddrPrefixLen) { mApnNameToTunnelConfig.put( apnName, - new TunnelConfig(ikeSession, tunnelCallback, srcIpv6Addr, srcIPv6AddrPrefixLen)); + new TunnelConfig( + ikeSession, + tunnelCallback, + tunnelMetrics, + srcIpv6Addr, + srcIPv6AddrPrefixLen)); Log.d(TAG, "Added apn: " + apnName + " to TunnelConfig"); } @VisibleForTesting - boolean isTunnelConfigContainExistApn(String apnName) { - boolean ret = mApnNameToTunnelConfig.containsKey(apnName); - return ret; + int incrementAndGetCurrentTokenForApn(String apnName) { + final int currentToken = + mApnNameToCurrentToken.compute( + apnName, (apn, token) -> token == null ? 0 : token + 1); + Log.d(TAG, "Added token: " + currentToken + " for apn: " + apnName); + return currentToken; } @VisibleForTesting - List<InetAddress> getAddressForNetwork(Network network, Context context) { - List<InetAddress> ret = IwlanHelper.getAddressesForNetwork(network, context); - return ret; + boolean isN1ModeSupported() { + int[] nrCarrierCaps = + getConfig(CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY); + Log.d(TAG, "KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY : " + Arrays.toString(nrCarrierCaps)); + if (Arrays.stream(nrCarrierCaps) + .anyMatch(cap -> cap == CarrierConfigManager.CARRIER_NR_AVAILABILITY_SA)) { + return true; + } else return false; } @VisibleForTesting - EpdgSelector.EpdgSelectorCallback getSelectorCallback() { - return mSelectorCallback; + boolean isTunnelConfigContainExistApn(String apnName) { + return mApnNameToTunnelConfig.containsKey(apnName); + } + + @VisibleForTesting + List<InetAddress> getAddressForNetwork(Network network, Context context) { + return IwlanHelper.getAllAddressesForNetwork(network, context); } @VisibleForTesting @@ -2039,7 +2293,9 @@ public class EpdgTunnelManager { @VisibleForTesting void sendSelectionRequestComplete( - ArrayList<InetAddress> validIPList, IwlanError result, int transactionId) { + List<InetAddress> validIPList, IwlanError result, int transactionId) { + mEpdgServerSelectionDuration = System.currentTimeMillis() - mEpdgServerSelectionStartTime; + mEpdgServerSelectionStartTime = 0; EpdgSelectorResult epdgSelectorResult = new EpdgSelectorResult(validIPList, result, transactionId); mHandler.sendMessage( @@ -2053,14 +2309,58 @@ public class EpdgTunnelManager { || proto == ApnSetting.PROTOCOL_IPV6); } + boolean isObsoleteToken(String apnName, int token) { + if (!mApnNameToCurrentToken.containsKey(apnName)) { + return true; + } + return token != mApnNameToCurrentToken.get(apnName); + } + + private static String eventToString(int event) { + switch (event) { + case EVENT_TUNNEL_BRINGUP_REQUEST: + return "EVENT_TUNNEL_BRINGUP_REQUEST"; + case EVENT_TUNNEL_BRINGDOWN_REQUEST: + return "EVENT_TUNNEL_BRINGDOWN_REQUEST"; + case EVENT_CHILD_SESSION_OPENED: + return "EVENT_CHILD_SESSION_OPENED"; + case EVENT_CHILD_SESSION_CLOSED: + return "EVENT_CHILD_SESSION_CLOSED"; + case EVENT_IKE_SESSION_CLOSED: + return "EVENT_IKE_SESSION_CLOSED"; + case EVENT_EPDG_ADDRESS_SELECTION_REQUEST_COMPLETE: + return "EVENT_EPDG_ADDRESS_SELECTION_REQUEST_COMPLETE"; + case EVENT_IPSEC_TRANSFORM_CREATED: + return "EVENT_IPSEC_TRANSFORM_CREATED"; + case EVENT_IPSEC_TRANSFORM_DELETED: + return "EVENT_IPSEC_TRANSFORM_DELETED"; + case EVENT_UPDATE_NETWORK: + return "EVENT_UPDATE_NETWORK"; + case EVENT_IKE_SESSION_OPENED: + return "EVENT_IKE_SESSION_OPENED"; + case EVENT_IKE_SESSION_CONNECTION_INFO_CHANGED: + return "EVENT_IKE_SESSION_CONNECTION_INFO_CHANGED"; + case EVENT_IKE_3GPP_DATA_RECEIVED: + return "EVENT_IKE_3GPP_DATA_RECEIVED"; + default: + return "Unknown(" + event + ")"; + } + } + @VisibleForTesting - TmIkeSessionCallback getTmIkeSessionCallback(String apnName) { - return new TmIkeSessionCallback(apnName); + TmIkeSessionCallback getTmIkeSessionCallback(String apnName, int token) { + return new TmIkeSessionCallback(apnName, token); } @VisibleForTesting - void setIsEpdgAddressSelected(boolean value) { - mIsEpdgAddressSelected = value; + void onConnectedToEpdg(boolean hasConnected) { + mHasConnectedToEpdg = hasConnected; + if (mHasConnectedToEpdg) { + mIkeSessionNetwork = mDefaultNetwork; + } else { + mIkeSessionNetwork = null; + mEpdgAddress = null; + } } @VisibleForTesting @@ -2069,6 +2369,14 @@ public class EpdgTunnelManager { } @VisibleForTesting + int getCurrentTokenForApn(String apnName) { + if (!mApnNameToCurrentToken.containsKey(apnName)) { + throw new IllegalArgumentException("There is no token for apn: " + apnName); + } + return mApnNameToCurrentToken.get(apnName); + } + + @VisibleForTesting long reportIwlanError(String apnName, IwlanError error) { return ErrorPolicyManager.getInstance(mContext, mSlotId).reportIwlanError(apnName, error); } @@ -2089,9 +2397,49 @@ public class EpdgTunnelManager { return ErrorPolicyManager.getInstance(mContext, mSlotId).canBringUpTunnel(apnName); } - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + @VisibleForTesting + void setEpdgAddress(InetAddress inetAddress) { + mEpdgAddress = inetAddress; + } + + @VisibleForTesting + IpPreferenceConflict isIpPreferenceConflictsWithNetwork( + @CarrierConfigManager.Iwlan.EpdgAddressIpPreference int ipPreference) { + List<InetAddress> localAddresses = getAddressForNetwork(mDefaultNetwork, mContext); + if (localAddresses == null || localAddresses.size() == 0) { + Log.e(TAG, "No local addresses available for Network " + mDefaultNetwork); + return new IpPreferenceConflict(true, IwlanError.EPDG_SELECTOR_SERVER_SELECTION_FAILED); + } else if (!IwlanHelper.hasIpv6Address(localAddresses) + && ipPreference == CarrierConfigManager.Iwlan.EPDG_ADDRESS_IPV6_ONLY) { + Log.e( + TAG, + "ePDG IP preference: " + + ipPreference + + " conflicts with source IP type: " + + EpdgSelector.PROTO_FILTER_IPV4); + return new IpPreferenceConflict(true, IwlanError.EPDG_ADDRESS_ONLY_IPV6_ALLOWED); + } else if (!IwlanHelper.hasIpv4Address(localAddresses) + && ipPreference == CarrierConfigManager.Iwlan.EPDG_ADDRESS_IPV4_ONLY) { + // b/209938719 allows Iwlan to support VoWiFi for IPv4 ePDG server while on IPv6 WiFi. + // Iwlan will receive a IPv4 address which is embedded in stacked IPv6 address. By using + // this IPv4 address, UE will connect to IPv4 ePDG server through XLAT. However, there + // are issues on connecting ePDG server through XLAT. Will allow IPV4_ONLY on IPv6 WiFi + // after the issues are resolved. + Log.e( + TAG, + "ePDG IP preference: " + + ipPreference + + " conflicts with source IP type: " + + EpdgSelector.PROTO_FILTER_IPV6); + return new IpPreferenceConflict(true, IwlanError.EPDG_ADDRESS_ONLY_IPV4_ALLOWED); + } + return new IpPreferenceConflict(); + } + + public void dump(PrintWriter pw) { pw.println("---- EpdgTunnelManager ----"); - pw.println("mIsEpdgAddressSelected: " + mIsEpdgAddressSelected); + pw.println("mHasConnectedToEpdg: " + mHasConnectedToEpdg); + pw.println("mIkeSessionNetwork: " + mIkeSessionNetwork); if (mEpdgAddress != null) { pw.println("mEpdgAddress: " + mEpdgAddress); } diff --git a/src/com/google/android/iwlan/epdg/IkeSessionState.java b/src/com/google/android/iwlan/epdg/IkeSessionState.java new file mode 100644 index 0000000..ad3d5e3 --- /dev/null +++ b/src/com/google/android/iwlan/epdg/IkeSessionState.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 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.google.android.iwlan.epdg; + +import com.google.android.iwlan.IwlanError; + +/** A state machine that infers the current IkeSession state. */ +enum IkeSessionState { + NO_IKE_SESSION { + @Override + public int getErrorType() { + return IwlanError.NO_ERROR; + } + }, + IKE_SESSION_INIT_IN_PROGRESS { + @Override + public int getErrorType() { + return IwlanError.IKE_INIT_TIMEOUT; + } + }, + IKE_MOBILITY_IN_PROGRESS { + @Override + public int getErrorType() { + return IwlanError.IKE_MOBILITY_TIMEOUT; + } + }, + CHILD_SESSION_OPENED { + @Override + public int getErrorType() { + return IwlanError.IKE_DPD_TIMEOUT; + } + }; + + /** + * Called when IkeSession report error with IkeIOException, check current IkeSession state and + * return corresponding time out error. + * + * @return NO_ERROR or IWLAN IKE time out error + */ + public abstract int getErrorType(); +} diff --git a/src/com/google/android/iwlan/epdg/SrvDnsResolver.java b/src/com/google/android/iwlan/epdg/SrvDnsResolver.java index 1ae9cba..192fbb8 100644 --- a/src/com/google/android/iwlan/epdg/SrvDnsResolver.java +++ b/src/com/google/android/iwlan/epdg/SrvDnsResolver.java @@ -36,6 +36,7 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -146,7 +147,7 @@ final class SrvDnsResolver { // Parses the Answers section of a DnsPacket to construct and return a mapping // of Domain Name strings to their corresponding SRV record. public @NonNull Map<String, SrvRecord> parseSrvRecords() throws ParseException { - final HashMap<String, SrvRecord> targetNameToSrvRecord = new HashMap<>(); + final HashMap<String, SrvRecord> targetNameToSrvRecord = new LinkedHashMap<>(); if (mHeader.getRecordCount(ANSECTION) == 0) return targetNameToSrvRecord; for (final DnsRecord ansSec : mRecords[ANSECTION]) { diff --git a/src/com/google/android/iwlan/epdg/TunnelSetupRequest.java b/src/com/google/android/iwlan/epdg/TunnelSetupRequest.java index 4086c22..d1ed9dd 100644 --- a/src/com/google/android/iwlan/epdg/TunnelSetupRequest.java +++ b/src/com/google/android/iwlan/epdg/TunnelSetupRequest.java @@ -29,8 +29,6 @@ public abstract class TunnelSetupRequest { abstract int apnIpProtocol(); - abstract Network network(); - abstract Optional<InetAddress> srcIpv4Address(); abstract Optional<InetAddress> srcIpv6Address(); @@ -58,8 +56,6 @@ public abstract class TunnelSetupRequest { public abstract Builder setApnIpProtocol(int protocol); - public abstract Builder setNetwork(Network network); - public Builder setSrcIpv4Address(InetAddress srcIpv4Address) { return setSrcIpv4Address(Optional.ofNullable(srcIpv4Address)); } diff --git a/src/com/google/android/iwlan/proto/MetricsAtom.java b/src/com/google/android/iwlan/proto/MetricsAtom.java new file mode 100644 index 0000000..7ecf464 --- /dev/null +++ b/src/com/google/android/iwlan/proto/MetricsAtom.java @@ -0,0 +1,182 @@ +/* + * Copyright 2020 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.google.android.iwlan.proto; + +import android.net.ipsec.ike.exceptions.IkeIOException; +import android.net.ipsec.ike.exceptions.IkeInternalException; + +import com.google.android.iwlan.IwlanError; +import com.google.android.iwlan.IwlanStatsLog; + +public class MetricsAtom { + private int mMessageId; + private int mApnType; + private boolean mIsHandover; + private String mEpdgServerAddress; + private int mSourceRat; + private boolean mIsCellularRoaming; + private boolean mIsNetworkConnected; + private int mTransportType; + private int mSetupRequestResult; + private int mIwlanError; + private int mDataCallFailCause; + private int mProcessingDurationMillis; + private int mEpdgServerSelectionDurationMillis; + private int mIkeTunnelEstablishmentDurationMillis; + private int mTunnelState; + private int mHandoverFailureMode; + private int mRetryDurationMillis; + private int mWifiSignalValue; + private String mIwlanErrorWrappedClassname; + private String mIwlanErrorWrappedStackFirstFrame; + + public void setMessageId(int messageId) { + this.mMessageId = messageId; + } + + public void setApnType(int apnType) { + this.mApnType = apnType; + } + + public void setIsHandover(boolean isHandover) { + this.mIsHandover = isHandover; + } + + public void setEpdgServerAddress(String epdgServerAddress) { + this.mEpdgServerAddress = epdgServerAddress; + } + + public void setSourceRat(int sourceRat) { + this.mSourceRat = sourceRat; + } + + public void setIsCellularRoaming(boolean isCellularRoaming) { + this.mIsCellularRoaming = isCellularRoaming; + } + + public void setIsNetworkConnected(boolean isNetworkConnected) { + this.mIsNetworkConnected = isNetworkConnected; + } + + public void setTransportType(int transportType) { + this.mTransportType = transportType; + } + + public void setSetupRequestResult(int setupRequestResult) { + this.mSetupRequestResult = setupRequestResult; + } + + public void setIwlanError(int iwlanError) { + this.mIwlanError = iwlanError; + } + + public void setDataCallFailCause(int dataCallFailCause) { + this.mDataCallFailCause = dataCallFailCause; + } + + public void setProcessingDurationMillis(int processingDurationMillis) { + this.mProcessingDurationMillis = processingDurationMillis; + } + + public void setEpdgServerSelectionDurationMillis(int epdgServerSelectionDurationMillis) { + this.mEpdgServerSelectionDurationMillis = epdgServerSelectionDurationMillis; + } + + public void setIkeTunnelEstablishmentDurationMillis(int ikeTunnelEstablishmentDurationMillis) { + this.mIkeTunnelEstablishmentDurationMillis = ikeTunnelEstablishmentDurationMillis; + } + + public void setTunnelState(int tunnelState) { + this.mTunnelState = tunnelState; + } + + public void setHandoverFailureMode(int handoverFailureMode) { + this.mHandoverFailureMode = handoverFailureMode; + } + + public void setRetryDurationMillis(int retryDurationMillis) { + this.mRetryDurationMillis = retryDurationMillis; + } + + public void setWifiSignalValue(int wifiSignalValue) { + this.mWifiSignalValue = wifiSignalValue; + } + + public void setIwlanErrorWrappedClassnameAndStack(IwlanError iwlanError) { + Throwable iwlanErrorWrapped = iwlanError.getException(); + if (iwlanErrorWrapped instanceof IkeInternalException + || iwlanErrorWrapped instanceof IkeIOException) { + iwlanErrorWrapped = iwlanErrorWrapped.getCause(); + } + + if (iwlanErrorWrapped == null) { + this.mIwlanErrorWrappedClassname = null; + this.mIwlanErrorWrappedStackFirstFrame = null; + return; + } + + this.mIwlanErrorWrappedClassname = iwlanErrorWrapped.getClass().getCanonicalName(); + + StackTraceElement[] iwlanErrorWrappedStackTraceElements = iwlanErrorWrapped.getStackTrace(); + this.mIwlanErrorWrappedStackFirstFrame = + iwlanErrorWrappedStackTraceElements.length != 0 + ? iwlanErrorWrappedStackTraceElements[0].toString() + : null; + } + + public String getIwlanErrorWrappedClassname() { + return mIwlanErrorWrappedClassname; + } + + public String getIwlanErrorWrappedStackFirstFrame() { + return mIwlanErrorWrappedStackFirstFrame; + } + + public void sendMetricsData() { + if (mMessageId == IwlanStatsLog.IWLAN_SETUP_DATA_CALL_RESULT_REPORTED) { + IwlanStatsLog.write( + mMessageId, + mApnType, + mIsHandover, + mEpdgServerAddress, + mSourceRat, + mIsCellularRoaming, + mIsNetworkConnected, + mTransportType, + mSetupRequestResult, + mIwlanError, + mDataCallFailCause, + mProcessingDurationMillis, + mEpdgServerSelectionDurationMillis, + mIkeTunnelEstablishmentDurationMillis, + mTunnelState, + mHandoverFailureMode, + mRetryDurationMillis, + mIwlanErrorWrappedClassname, + mIwlanErrorWrappedStackFirstFrame); + return; + } else if (mMessageId == IwlanStatsLog.IWLAN_PDN_DISCONNECTED_REASON_REPORTED) { + IwlanStatsLog.write( + mMessageId, + mDataCallFailCause, + mIsNetworkConnected, + mTransportType, + mWifiSignalValue); + return; + } + } +} diff --git a/test/com/google/android/iwlan/ErrorPolicyManagerTest.java b/test/com/google/android/iwlan/ErrorPolicyManagerTest.java index 3bc624b..1c15e41 100644 --- a/test/com/google/android/iwlan/ErrorPolicyManagerTest.java +++ b/test/com/google/android/iwlan/ErrorPolicyManagerTest.java @@ -20,6 +20,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.*; import static org.mockito.Mockito.mock; @@ -30,6 +31,7 @@ import android.content.Context; import android.content.res.AssetManager; import android.net.ipsec.ike.exceptions.IkeProtocolException; import android.os.PersistableBundle; +import android.os.test.TestLooper; import android.telephony.CarrierConfigManager; import android.telephony.DataFailCause; import android.telephony.SubscriptionInfo; @@ -39,6 +41,9 @@ import android.telephony.data.DataService; import androidx.test.InstrumentationRegistry; +import com.google.auto.value.AutoValue; + +import org.json.JSONArray; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -48,9 +53,80 @@ import org.mockito.MockitoSession; import org.mockito.quality.Strictness; import java.io.InputStream; +import java.util.List; import java.util.Map; +import java.util.Optional; public class ErrorPolicyManagerTest { + @AutoValue + abstract static class ErrorPolicyString { + abstract String errorType(); + + abstract List<String> errorDetails(); + + abstract List<String> retryArray(); + + abstract List<String> unthrottlingEvents(); + + abstract Optional<String> numAttemptsPerFqdn(); + + abstract Optional<String> handoverAttemptCount(); + + static Builder builder() { + return new AutoValue_ErrorPolicyManagerTest_ErrorPolicyString.Builder(); + } + + @AutoValue.Builder + abstract static class Builder { + abstract Builder setErrorType(String errorType); + + abstract Builder setErrorDetails(List<String> errorDetails); + + abstract Builder setRetryArray(List<String> retryArray); + + abstract Builder setUnthrottlingEvents(List<String> unthrottlingEvents); + + abstract Builder setNumAttemptsPerFqdn(String numAttemptsPerFqdn); + + abstract Builder setHandoverAttemptCount(String handoverAttemptCount); + + abstract ErrorPolicyString build(); + } + + String getErrorPolicyInString() { + StringBuilder errorPolicy = + new StringBuilder( + "\"ErrorType\": \"" + + errorType() + + "\"," + + "\"ErrorDetails\": [\"" + + String.join("\", \"", errorDetails()) + + "\"]," + + "\"RetryArray\": [\"" + + String.join("\", \"", retryArray()) + + "\"]," + + "\"UnthrottlingEvents\": [\"" + + String.join("\", \"", unthrottlingEvents()) + + "\"]"); + + numAttemptsPerFqdn() + .ifPresent( + numAttemptsPerFqdn -> + errorPolicy + .append(",\"NumAttemptsPerFqdn\": \"") + .append(numAttemptsPerFqdn) + .append("\"")); + handoverAttemptCount() + .ifPresent( + handoverAttemptCount -> + errorPolicy + .append(",\"HandoverAttemptCount\": \"") + .append(handoverAttemptCount) + .append("\"")); + return errorPolicy.toString(); + } + } + private static final String TAG = "ErrorPolicyManagerTest"; // @Rule public final MockitoRule mockito = MockitoJUnit.rule(); @@ -60,6 +136,9 @@ public class ErrorPolicyManagerTest { private static final int DEFAULT_SUBID = 0; private static final int TEST_CARRIER_ID = 1; + private TestLooper mTestLooper = new TestLooper(); + private long mMockedClockTime = 0; + @Mock private Context mMockContext; @Mock CarrierConfigManager mMockCarrierConfigManager; @Mock SubscriptionManager mMockSubscriptionManager; @@ -75,17 +154,22 @@ public class ErrorPolicyManagerTest { mStaticMockSession = mockitoSession() .mockStatic(IwlanDataService.class) + .spyStatic(IwlanHelper.class) .strictness(Strictness.LENIENT) .startMocking(); when(IwlanDataService.getDataServiceProvider(anyInt())) .thenReturn(mMockDataServiceProvider); + when(IwlanHelper.elapsedRealtime()).thenAnswer(i -> mMockedClockTime); AssetManager mockAssetManager = mock(AssetManager.class); Context context = InstrumentationRegistry.getTargetContext(); InputStream is = context.getResources().getAssets().open("defaultiwlanerrorconfig.json"); doReturn(mockAssetManager).when(mMockContext).getAssets(); doReturn(is).when(mockAssetManager).open(any()); setupMockForCarrierConfig(null); + ErrorPolicyManager.resetAllInstances(); mErrorPolicyManager = spy(ErrorPolicyManager.getInstance(mMockContext, DEFAULT_SLOT_INDEX)); + doReturn(mTestLooper.getLooper()).when(mErrorPolicyManager).getLooper(); + mErrorPolicyManager.initHandler(); } @After @@ -113,6 +197,10 @@ public class ErrorPolicyManagerTest { return buildIwlanIkeProtocolError(IkeProtocolException.ERROR_TYPE_CHILD_SA_NOT_FOUND); } + private static IwlanError buildIwlanIkeInternalAddressFailure() { + return buildIwlanIkeProtocolError(IkeProtocolException.ERROR_TYPE_INTERNAL_ADDRESS_FAILURE); + } + @Test public void testValidCarrierConfig() throws Exception { String apn = "ims"; @@ -122,17 +210,22 @@ public class ErrorPolicyManagerTest { + apn + "\"," + "\"ErrorTypes\": [{" - + getErrorTypeInJSON( - "IKE_PROTOCOL_ERROR_TYPE", - new String[] {"24", "34", "9000-9050"}, - new String[] {"4", "8", "16"}, - new String[] {"APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT"}) + + ErrorPolicyString.builder() + .setErrorType("IKE_PROTOCOL_ERROR_TYPE") + .setErrorDetails(List.of("24", "34", "9000-9050")) + .setRetryArray(List.of("4", "8", "16")) + .setUnthrottlingEvents( + List.of("APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT")) + .build() + .getErrorPolicyInString() + "}, {" - + getErrorTypeInJSON( - "GENERIC_ERROR_TYPE", - new String[] {"SERVER_SELECTION_FAILED"}, - new String[] {"0"}, - new String[] {"APM_ENABLE_EVENT"}) + + ErrorPolicyString.builder() + .setErrorType("GENERIC_ERROR_TYPE") + .setErrorDetails(List.of("SERVER_SELECTION_FAILED")) + .setRetryArray(List.of("0")) + .setUnthrottlingEvents(List.of("APM_ENABLE_EVENT")) + .build() + .getErrorPolicyInString() + "}]" + "}]"; @@ -143,8 +236,7 @@ public class ErrorPolicyManagerTest { .mHandler .obtainMessage(IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT) .sendToTarget(); - - sleep(1000); + mTestLooper.dispatchAll(); // IKE_PROTOCOL_ERROR_TYPE(24) and retryArray = 4,8,16 IwlanError iwlanError = buildIwlanIkeAuthFailedError(); @@ -187,7 +279,7 @@ public class ErrorPolicyManagerTest { time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); assertEquals(10, time); time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); - assertEquals(10, time); + assertEquals(20, time); } @Test @@ -199,11 +291,14 @@ public class ErrorPolicyManagerTest { + apn + "\"," + "\"ErrorTypes\": [{" - + getErrorTypeInJSON( - "IKE_PROTOCOL_ERROR_TYPE", - new String[] {"WRONG_ERROR_DETAIL"}, - new String[] {"4", "8", "16"}, - new String[] {"APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT"}) + + ErrorPolicyString.builder() + .setErrorType("IKE_PROTOCOL_ERROR_TYPE") + .setErrorDetails(List.of("WRONG_ERROR_DETAIL")) + .setRetryArray(List.of("4", "8", "16")) + .setUnthrottlingEvents( + List.of("APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT")) + .build() + .getErrorPolicyInString() + "}]" + "}]"; @@ -214,13 +309,16 @@ public class ErrorPolicyManagerTest { .mHandler .obtainMessage(IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT) .sendToTarget(); - - sleep(1000); + mTestLooper.dispatchAll(); // Fallback to default Iwlan error policy for IKE_PROTOCOL_ERROR_TYPE(24) because of failed // parsing (or lack of explicit carrier-defined policy). IwlanError iwlanError = buildIwlanIkeAuthFailedError(); long time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); + assertEquals(5, time); + time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); + assertEquals(10, time); + time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); assertEquals(10, time); time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); assertEquals(20, time); @@ -228,10 +326,30 @@ public class ErrorPolicyManagerTest { assertEquals(40, time); time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); assertEquals(80, time); + + iwlanError = buildIwlanIkeProtocolError(9002); + time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); + assertEquals(5, time); time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); - assertEquals(160, time); + assertEquals(10, time); time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); - assertEquals(86400, time); + assertEquals(10, time); + time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); + assertEquals(20, time); + time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); + assertEquals(40, time); + time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); + assertEquals(80, time); + + iwlanError = buildIwlanIkeInternalAddressFailure(); + time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); + assertEquals(0, time); + time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); + assertEquals(0, time); + time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); + assertEquals(0, time); + time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); + assertEquals(10, time); } @Test @@ -243,17 +361,22 @@ public class ErrorPolicyManagerTest { + apn + "\"," + "\"ErrorTypes\": [{" - + getErrorTypeInJSON( - "IKE_PROTOCOL_ERROR_TYPE", - new String[] {"24", "34"}, - new String[] {"4", "8", "16"}, - new String[] {"APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT"}) + + ErrorPolicyString.builder() + .setErrorType("IKE_PROTOCOL_ERROR_TYPE") + .setErrorDetails(List.of("24", "34")) + .setRetryArray(List.of("4", "8", "16")) + .setUnthrottlingEvents( + List.of("APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT")) + .build() + .getErrorPolicyInString() + "}, {" - + getErrorTypeInJSON( - "IKE_PROTOCOL_ERROR_TYPE", - new String[] {"*"}, - new String[] {"0"}, - new String[] {"APM_ENABLE_EVENT"}) + + ErrorPolicyString.builder() + .setErrorType("IKE_PROTOCOL_ERROR_TYPE") + .setErrorDetails(List.of("*")) + .setRetryArray(List.of("0")) + .setUnthrottlingEvents(List.of("APM_ENABLE_EVENT")) + .build() + .getErrorPolicyInString() + "}]" + "}]"; PersistableBundle bundle = new PersistableBundle(); @@ -263,8 +386,7 @@ public class ErrorPolicyManagerTest { .mHandler .obtainMessage(IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT) .sendToTarget(); - - sleep(1000); + mTestLooper.dispatchAll(); mErrorPolicyManager.logErrorPolicies(); @@ -297,17 +419,22 @@ public class ErrorPolicyManagerTest { + apn + "\"," + "\"ErrorTypes\": [{" - + getErrorTypeInJSON( - "IKE_PROTOCOL_ERROR_TYPE", - new String[] {"24", "34"}, - new String[] {"4", "8", "16"}, - new String[] {"APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT"}) + + ErrorPolicyString.builder() + .setErrorType("IKE_PROTOCOL_ERROR_TYPE") + .setErrorDetails(List.of("24", "34")) + .setRetryArray(List.of("4", "8", "16")) + .setUnthrottlingEvents( + List.of("APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT")) + .build() + .getErrorPolicyInString() + "}, {" - + getErrorTypeInJSON( - "GENERIC_ERROR_TYPE", - new String[] {"SERVER_SELECTION_FAILED"}, - new String[] {"0"}, - new String[] {"APM_ENABLE_EVENT"}) + + 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(); @@ -317,8 +444,7 @@ public class ErrorPolicyManagerTest { .mHandler .obtainMessage(IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT) .sendToTarget(); - - sleep(1000); + mTestLooper.dispatchAll(); // IKE_PROTOCOL_ERROR_TYPE(24) and retryArray = 4,8,16 IwlanError iwlanError = buildIwlanIkeAuthFailedError(); @@ -328,7 +454,7 @@ public class ErrorPolicyManagerTest { boolean bringUpTunnel = mErrorPolicyManager.canBringUpTunnel(apn); assertFalse(bringUpTunnel); - sleep(4000); + advanceClockByTimeMs(4000); bringUpTunnel = mErrorPolicyManager.canBringUpTunnel(apn); assertTrue(bringUpTunnel); @@ -349,17 +475,22 @@ public class ErrorPolicyManagerTest { + apn + "\"," + "\"ErrorTypes\": [{" - + getErrorTypeInJSON( - "IKE_PROTOCOL_ERROR_TYPE", - new String[] {"24", "34"}, - new String[] {"4", "8", "16"}, - new String[] {"APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT"}) + + ErrorPolicyString.builder() + .setErrorType("IKE_PROTOCOL_ERROR_TYPE") + .setErrorDetails(List.of("24", "34")) + .setRetryArray(List.of("4", "8", "16")) + .setUnthrottlingEvents( + List.of("APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT")) + .build() + .getErrorPolicyInString() + "}, {" - + getErrorTypeInJSON( - "GENERIC_ERROR_TYPE", - new String[] {"SERVER_SELECTION_FAILED"}, - new String[] {"0"}, - new String[] {"APM_ENABLE_EVENT"}) + + 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(); @@ -369,8 +500,7 @@ public class ErrorPolicyManagerTest { .mHandler .obtainMessage(IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT) .sendToTarget(); - - sleep(1000); + mTestLooper.dispatchAll(); // IKE_PROTOCOL_ERROR_TYPE(24) and retryArray = 4,8,16 IwlanError iwlanError = buildIwlanIkeAuthFailedError(); @@ -396,17 +526,22 @@ public class ErrorPolicyManagerTest { + apn + "\"," + "\"ErrorTypes\": [{" - + getErrorTypeInJSON( - "IKE_PROTOCOL_ERROR_TYPE", - new String[] {"24", "34"}, - new String[] {"6", "12", "24"}, - new String[] {"APM_ENABLE_EVENT", "WIFI_DISABLE_EVENT"}) + + ErrorPolicyString.builder() + .setErrorType("IKE_PROTOCOL_ERROR_TYPE") + .setErrorDetails(List.of("24", "34")) + .setRetryArray(List.of("6", "12", "24")) + .setUnthrottlingEvents( + List.of("APM_ENABLE_EVENT", "WIFI_DISABLE_EVENT")) + .build() + .getErrorPolicyInString() + "}, {" - + getErrorTypeInJSON( - "GENERIC_ERROR_TYPE", - new String[] {"SERVER_SELECTION_FAILED"}, - new String[] {"0"}, - new String[] {"APM_DISABLE_EVENT"}) + + 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(); @@ -416,7 +551,7 @@ public class ErrorPolicyManagerTest { .mHandler .obtainMessage(IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT) .sendToTarget(); - sleep(1000); + mTestLooper.dispatchAll(); // IKE_PROTOCOL_ERROR_TYPE(24) and retryArray = 6, 12, 24 IwlanError iwlanError = buildIwlanIkeAuthFailedError(); @@ -427,7 +562,7 @@ public class ErrorPolicyManagerTest { .mHandler .obtainMessage(IwlanEventListener.WIFI_DISABLE_EVENT) .sendToTarget(); - sleep(500); + advanceClockByTimeMs(500); verify(mMockDataServiceProvider, times(1)).notifyApnUnthrottled(eq(apn)); boolean bringUpTunnel = mErrorPolicyManager.canBringUpTunnel(apn); @@ -447,17 +582,22 @@ public class ErrorPolicyManagerTest { + apn + "\"," + "\"ErrorTypes\": [{" - + getErrorTypeInJSON( - "IKE_PROTOCOL_ERROR_TYPE", - new String[] {"24", "34"}, - new String[] {"6", "12", "24"}, - new String[] {"WIFI_CALLING_DISABLE_EVENT", "WIFI_DISABLE_EVENT"}) + + 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")) + .build() + .getErrorPolicyInString() + "}, {" - + getErrorTypeInJSON( - "GENERIC_ERROR_TYPE", - new String[] {"SERVER_SELECTION_FAILED"}, - new String[] {"0"}, - new String[] {"APM_DISABLE_EVENT"}) + + 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(); @@ -467,7 +607,7 @@ public class ErrorPolicyManagerTest { .mHandler .obtainMessage(IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT) .sendToTarget(); - sleep(1000); + mTestLooper.dispatchAll(); // IKE_PROTOCOL_ERROR_TYPE(24) and retryArray = 6, 12, 24 IwlanError iwlanError = buildIwlanIkeAuthFailedError(); @@ -478,7 +618,7 @@ public class ErrorPolicyManagerTest { .mHandler .obtainMessage(IwlanEventListener.WIFI_CALLING_DISABLE_EVENT) .sendToTarget(); - sleep(500); + advanceClockByTimeMs(500); verify(mMockDataServiceProvider, times(1)).notifyApnUnthrottled(eq(apn)); boolean bringUpTunnel = mErrorPolicyManager.canBringUpTunnel(apn); @@ -498,17 +638,22 @@ public class ErrorPolicyManagerTest { + apn + "\"," + "\"ErrorTypes\": [{" - + getErrorTypeInJSON( - "IKE_PROTOCOL_ERROR_TYPE", - new String[] {"24", "34"}, - new String[] {"4", "8", "16"}, - new String[] {"APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT"}) + + ErrorPolicyString.builder() + .setErrorType("IKE_PROTOCOL_ERROR_TYPE") + .setErrorDetails(List.of("24", "34")) + .setRetryArray(List.of("4", "8", "16")) + .setUnthrottlingEvents( + List.of("APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT")) + .build() + .getErrorPolicyInString() + "}, {" - + getErrorTypeInJSON( - "GENERIC_ERROR_TYPE", - new String[] {"SERVER_SELECTION_FAILED"}, - new String[] {"0"}, - new String[] {"APM_DISABLE_EVENT"}) + + 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(); @@ -518,7 +663,7 @@ public class ErrorPolicyManagerTest { .mHandler .obtainMessage(IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT) .sendToTarget(); - sleep(1000); + mTestLooper.dispatchAll(); // IKE_PROTOCOL_ERROR_TYPE(24) and retryArray = 4,8,16 IwlanError iwlanError = buildIwlanIkeAuthFailedError(); @@ -529,7 +674,7 @@ public class ErrorPolicyManagerTest { .mHandler .obtainMessage(IwlanEventListener.APM_ENABLE_EVENT) .sendToTarget(); - sleep(500); + advanceClockByTimeMs(500); verify(mMockDataServiceProvider, times(1)).notifyApnUnthrottled(eq(apn)); boolean bringUpTunnel = mErrorPolicyManager.canBringUpTunnel(apn); @@ -550,17 +695,22 @@ public class ErrorPolicyManagerTest { + apn1 + "\"," + "\"ErrorTypes\": [{" - + getErrorTypeInJSON( - "IKE_PROTOCOL_ERROR_TYPE", - new String[] {"24", "34"}, - new String[] {"4", "8", "16"}, - new String[] {"APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT"}) + + ErrorPolicyString.builder() + .setErrorType("IKE_PROTOCOL_ERROR_TYPE") + .setErrorDetails(List.of("24", "34")) + .setRetryArray(List.of("4", "8", "16")) + .setUnthrottlingEvents( + List.of("APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT")) + .build() + .getErrorPolicyInString() + "}, {" - + getErrorTypeInJSON( - "GENERIC_ERROR_TYPE", - new String[] {"SERVER_SELECTION_FAILED"}, - new String[] {"0"}, - new String[] {"APM_ENABLE_EVENT"}) + + 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(); @@ -570,8 +720,7 @@ public class ErrorPolicyManagerTest { .mHandler .obtainMessage(IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT) .sendToTarget(); - - sleep(1000); + mTestLooper.dispatchAll(); // IKE_PROTOCOL_ERROR_TYPE(24) and retryArray = 4,8,16 IwlanError iwlanError = buildIwlanIkeAuthFailedError(); @@ -607,17 +756,22 @@ public class ErrorPolicyManagerTest { + apn + "\"," + "\"ErrorTypes\": [{" - + getErrorTypeInJSON( - "IKE_PROTOCOL_ERROR_TYPE", - new String[] {"24", "34"}, - new String[] {"10", "15", "20"}, - new String[] {"APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT"}) + + ErrorPolicyString.builder() + .setErrorType("IKE_PROTOCOL_ERROR_TYPE") + .setErrorDetails(List.of("24", "34")) + .setRetryArray(List.of("10", "15", "20")) + .setUnthrottlingEvents( + List.of("APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT")) + .build() + .getErrorPolicyInString() + "}, {" - + getErrorTypeInJSON( - "GENERIC_ERROR_TYPE", - new String[] {"SERVER_SELECTION_FAILED"}, - new String[] {"0"}, - new String[] {"APM_ENABLE_EVENT"}) + + ErrorPolicyString.builder() + .setErrorType("GENERIC_ERROR_TYPE") + .setErrorDetails(List.of("SERVER_SELECTION_FAILED")) + .setRetryArray(List.of("0")) + .setUnthrottlingEvents(List.of("APM_ENABLE_EVENT")) + .build() + .getErrorPolicyInString() + "}]" + "}]"; @@ -628,8 +782,7 @@ public class ErrorPolicyManagerTest { .mHandler .obtainMessage(IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT) .sendToTarget(); - - sleep(1000); + mTestLooper.dispatchAll(); // IKE_PROTOCOL_ERROR_TYPE(24) and retryArray = 4,8,16 IwlanError iwlanError = buildIwlanIkeAuthFailedError(); @@ -638,9 +791,9 @@ public class ErrorPolicyManagerTest { time = Math.round((double) mErrorPolicyManager.getCurrentRetryTimeMs(apn) / 1000); assertEquals(time, 2); - // sleep for 2 seconds and make sure that we can bring up tunnel after 2 secs + // advanceClockByTimeMs for 2 seconds and make sure that we can bring up tunnel after 2 secs // as back off time - 2 secs should override the retry time in policy - 10 secs - sleep(2000); + advanceClockByTimeMs(2000); boolean bringUpTunnel = mErrorPolicyManager.canBringUpTunnel(apn); assertTrue(bringUpTunnel); @@ -661,6 +814,207 @@ public class ErrorPolicyManagerTest { } @Test + public void testErrorPolicyWithNumAttemptsPerFqdn() throws Exception { + // ErrorPolicyManager#getCurrentFqdnIndex() is tested when the ErrorType + // parameter "NumAttemptsPerFqdn" is configured. + String apn = "ims"; + String config = + "[{" + + "\"ApnName\": \"" + + apn + + "\"," + + "\"ErrorTypes\": [{" + + ErrorPolicyString.builder() + .setErrorType("IKE_PROTOCOL_ERROR_TYPE") + .setErrorDetails(List.of("15500")) /* CONGESTION */ + .setRetryArray( + List.of( + "0", "0", "300", "600", "1200", "0", "0", "0", + "300", "600", "1200", "-1")) + .setUnthrottlingEvents( + List.of( + "APM_ENABLE_EVENT", + "WIFI_DISABLE_EVENT", + "WIFI_CALLING_DISABLE_EVENT")) + .setNumAttemptsPerFqdn("6") + .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(); + assertEquals(DataFailCause.NONE, mErrorPolicyManager.getMostRecentDataFailCause()); + + // IKE_PROTOCOL_ERROR_TYPE(15500) + // UE constructs 2 PLMN FQDNs. + IwlanError iwlanError = buildIwlanIkeProtocolError(15500 /* CONGESTION */); + + long time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); + assertEquals(0, time); + assertEquals( + DataFailCause.IWLAN_CONGESTION, mErrorPolicyManager.getMostRecentDataFailCause()); + + time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); + assertEquals(0, time); + time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); + assertEquals(300, time); + time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); + assertEquals(600, time); + + time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); + assertEquals(1200, time); + assertEquals(0, mErrorPolicyManager.getCurrentFqdnIndex(2)); + + // Cycles to next FQDN + time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); + assertEquals(0, time); + assertEquals(1, mErrorPolicyManager.getCurrentFqdnIndex(2)); + + time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); + assertEquals(0, time); + assertEquals(1, mErrorPolicyManager.getCurrentFqdnIndex(2)); + + time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); + assertEquals(0, time); + time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); + assertEquals(300, time); + time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); + assertEquals(600, time); + time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); + assertEquals(1200, time); + + // Steady state retry duration, cycles back to 1st FQDN. + time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); + assertEquals(1200, time); + assertEquals(0, mErrorPolicyManager.getCurrentFqdnIndex(2)); + } + + @Test + public void testShouldRetryWithInitialAttach() 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("4", "8", "16")) + .setUnthrottlingEvents( + List.of("APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT")) + .setHandoverAttemptCount("2") + .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 = 4,8,16 + IwlanError iwlanError = buildIwlanIkeAuthFailedError(); + long time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); + assertEquals(4, time); + assertFalse(mErrorPolicyManager.shouldRetryWithInitialAttach(apn)); + + time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); + assertEquals(8, time); + // Reached handover attempt count and error is IKE protocol error + assertTrue(mErrorPolicyManager.shouldRetryWithInitialAttach(apn)); + } + + @Test + public void testShouldRetryWithInitialAttachForInternalError() 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("4", "8", "16")) + .setUnthrottlingEvents( + List.of("APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT")) + .setHandoverAttemptCount("2") + .build() + .getErrorPolicyInString() + + "}, {" + + ErrorPolicyString.builder() + .setErrorType("GENERIC_ERROR_TYPE") + .setErrorDetails(List.of("SERVER_SELECTION_FAILED")) + .setRetryArray(List.of("0", "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(); + + // GENERIC_PROTOCOL_ERROR_TYPE - SERVER_SELECTION_FAILED and retryArray = 0, 0 + IwlanError iwlanError = new IwlanError(IwlanError.EPDG_SELECTOR_SERVER_SELECTION_FAILED); + long time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); + assertEquals(0, time); + assertFalse(mErrorPolicyManager.shouldRetryWithInitialAttach(apn)); + + time = mErrorPolicyManager.reportIwlanError(apn, iwlanError); + assertEquals(0, time); + // Should not retry with initial attach as the errors are not IKE_PROTOCOL_ERROR_TYPE + assertFalse(mErrorPolicyManager.shouldRetryWithInitialAttach(apn)); + } + + @Test + public void testHandoverAttemptCountInvalidErrorType() throws Exception { + String apn = "ims"; + String config = + "[{" + + "\"ApnName\": \"" + + apn + + "\"," + + "\"ErrorTypes\": [{" + + ErrorPolicyString.builder() + .setErrorType("GENERIC_ERROR_TYPE") + .setErrorDetails(List.of("*")) + .setRetryArray(List.of("4", "8", "16")) + .setUnthrottlingEvents( + List.of("APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT")) + .setHandoverAttemptCount("2") + .build() + .getErrorPolicyInString() + + "}]" + + "}]"; + + assertThrows( + IllegalArgumentException.class, + () -> mErrorPolicyManager.readErrorPolicies(new JSONArray(config))); + } + + @Test public void testErrorStats() throws Exception { String apn1 = "ims"; String apn2 = "mms"; @@ -701,31 +1055,9 @@ public class ErrorPolicyManagerTest { assertEquals(resultServerApn2, serverSelectionCountApn2); } - private String getErrorTypeInJSON( - String ErrorType, - String[] errorDetails, - String[] retryArray, - String[] unthrottlingEvents) { - return "\"ErrorType\": \"" - + ErrorType - + "\"," - + "\"ErrorDetails\": [\"" - + String.join("\", \"", errorDetails) - + "\"]," - + "\"RetryArray\": [\"" - + String.join("\", \"", retryArray) - + "\"]," - + "\"UnthrottlingEvents\": [\"" - + String.join("\", \"", unthrottlingEvents) - + "\"]"; - } - - private void sleep(long time) { - try { - Thread.sleep(time); - } catch (Exception e) { - e.printStackTrace(); - } + private void advanceClockByTimeMs(long time) { + mMockedClockTime += time; + mTestLooper.dispatchAll(); } private void setupMockForCarrierConfig(PersistableBundle bundle) { diff --git a/test/com/google/android/iwlan/IwlanDataServiceTest.java b/test/com/google/android/iwlan/IwlanDataServiceTest.java index a0414de..2f8f2c3 100644 --- a/test/com/google/android/iwlan/IwlanDataServiceTest.java +++ b/test/com/google/android/iwlan/IwlanDataServiceTest.java @@ -17,21 +17,46 @@ 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 com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.isNull; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.content.ContentResolver; import android.content.Context; import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; +import android.net.TelephonyNetworkSpecifier; +import android.net.ipsec.ike.exceptions.IkeInternalException; +import android.net.vcn.VcnTransportInfo; +import android.os.test.TestLooper; import android.telephony.AccessNetworkConstants.AccessNetworkType; import android.telephony.DataFailCause; import android.telephony.SubscriptionInfo; @@ -49,12 +74,12 @@ import android.telephony.ims.ImsMmTelManager; import com.google.android.iwlan.IwlanDataService.IwlanDataServiceProvider; import com.google.android.iwlan.IwlanDataService.IwlanDataServiceProvider.IwlanTunnelCallback; import com.google.android.iwlan.IwlanDataService.IwlanDataServiceProvider.TunnelState; -import com.google.android.iwlan.IwlanDataService.IwlanNetworkMonitorCallback; 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.TunnelLinkPropertiesTest; import com.google.android.iwlan.epdg.TunnelSetupRequest; +import com.google.android.iwlan.proto.MetricsAtom; import org.junit.After; import org.junit.Before; @@ -65,11 +90,13 @@ import org.mockito.MockitoAnnotations; import org.mockito.MockitoSession; import org.mockito.quality.Strictness; +import java.lang.reflect.Method; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.util.ArrayList; import java.util.Calendar; +import java.util.Collections; import java.util.Date; import java.util.List; import java.util.LongSummaryStatistics; @@ -79,6 +106,7 @@ import java.util.concurrent.TimeUnit; public class IwlanDataServiceTest { private static final int DEFAULT_SLOT_INDEX = 0; private static final int DEFAULT_SUB_INDEX = 0; + private static final int INVALID_SUB_INDEX = -1; private static final int LINK_MTU = 1280; private static final String TEST_APN_NAME = "ims"; private static final String IP_ADDRESS = "192.0.2.1"; @@ -96,26 +124,29 @@ public class IwlanDataServiceTest { @Mock private EpdgTunnelManager mMockEpdgTunnelManager; @Mock private IwlanDataServiceProvider mMockIwlanDataServiceProvider; @Mock private Network mMockNetwork; - @Mock private NetworkCapabilities mMockNetworkCapabilities; @Mock private TunnelLinkProperties mMockTunnelLinkProperties; @Mock private ErrorPolicyManager mMockErrorPolicyManager; @Mock private ImsManager mMockImsManager; @Mock private ImsMmTelManager mMockImsMmTelManager; @Mock private TelephonyManager mMockTelephonyManager; @Mock private EpdgSelector mMockEpdgSelector; - @Mock private LinkProperties mMockLinkProperties; @Mock private LinkAddress mMockIPv4LinkAddress; @Mock private LinkAddress mMockIPv6LinkAddress; @Mock private Inet4Address mMockInet4Address; @Mock private Inet6Address mMockInet6Address; + MockitoSession mStaticMockSession; + private LinkProperties mLinkProperties; private List<DataCallResponse> mResultDataCallList; private @DataServiceCallback.ResultCode int mResultCode; private CountDownLatch latch; private IwlanDataService mIwlanDataService; - private IwlanDataServiceProvider mIwlanDataServiceProvider; private IwlanDataServiceProvider mSpyIwlanDataServiceProvider; + private TestLooper mTestLooper = new TestLooper(); + private long mMockedCalendarTime; + private ArgumentCaptor<NetworkCallback> mNetworkCallbackCaptor = + ArgumentCaptor.forClass(NetworkCallback.class); private final class IwlanDataServiceCallback extends IDataServiceCallback.Stub { @@ -170,24 +201,32 @@ public class IwlanDataServiceTest { mStaticMockSession = mockitoSession() .mockStatic(EpdgSelector.class) + .mockStatic(EpdgTunnelManager.class) .mockStatic(ErrorPolicyManager.class) .mockStatic(IwlanBroadcastReceiver.class) - .mockStatic(IwlanHelper.class) + .mockStatic(SubscriptionManager.class) .strictness(Strictness.LENIENT) .startMocking(); when(mMockContext.getSystemService(eq(ConnectivityManager.class))) .thenReturn(mMockConnectivityManager); - when(mMockConnectivityManager.getNetworkCapabilities(eq(mMockNetwork))) - .thenReturn(mMockNetworkCapabilities); - when(mMockNetworkCapabilities.hasTransport(eq(TRANSPORT_CELLULAR))).thenReturn(false); - when(mMockNetworkCapabilities.hasTransport(eq(TRANSPORT_WIFI))).thenReturn(true); when(mMockContext.getSystemService(eq(SubscriptionManager.class))) .thenReturn(mMockSubscriptionManager); + doNothing() + .when(mMockConnectivityManager) + .registerSystemDefaultNetworkCallback(mNetworkCallbackCaptor.capture(), any()); + + when(EpdgTunnelManager.getInstance(mMockContext, DEFAULT_SLOT_INDEX)) + .thenReturn(mMockEpdgTunnelManager); when(mMockSubscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(anyInt())) .thenReturn(mMockSubscriptionInfo); + when(mMockSubscriptionManager.getDefaultDataSubscriptionId()).thenReturn(DEFAULT_SUB_INDEX); + when(mMockSubscriptionManager.getSlotIndex(DEFAULT_SUB_INDEX)) + .thenReturn(DEFAULT_SLOT_INDEX); + when(mMockSubscriptionManager.getSlotIndex(DEFAULT_SUB_INDEX + 1)) + .thenReturn(DEFAULT_SLOT_INDEX + 1); when(mMockSubscriptionInfo.getSubscriptionId()).thenReturn(DEFAULT_SUB_INDEX); @@ -214,50 +253,335 @@ public class IwlanDataServiceTest { when(mMockIPv6LinkAddress.getAddress()).thenReturn(mMockInet6Address); mIwlanDataService = spy(new IwlanDataService()); + // Injects the test looper into the IwlanDataServiceHandler + doReturn(mTestLooper.getLooper()).when(mIwlanDataService).getLooper(); mIwlanDataService.setAppContext(mMockContext); - mIwlanDataServiceProvider = - (IwlanDataServiceProvider) - mIwlanDataService.onCreateDataServiceProvider(DEFAULT_SLOT_INDEX); - mSpyIwlanDataServiceProvider = spy(mIwlanDataServiceProvider); + mSpyIwlanDataServiceProvider = + spy( + (IwlanDataServiceProvider) + mIwlanDataService.onCreateDataServiceProvider(DEFAULT_SLOT_INDEX)); + mTestLooper.dispatchAll(); + + when(Calendar.getInstance().getTime()).thenAnswer(i -> mMockedCalendarTime); + + mLinkProperties = new LinkProperties(); + mLinkProperties.setInterfaceName("wlan0"); + mLinkProperties.addLinkAddress(mMockIPv4LinkAddress); + + when(mMockConnectivityManager.getLinkProperties(eq(mMockNetwork))) + .thenReturn(mLinkProperties); } @After public void cleanUp() throws Exception { mStaticMockSession.finishMocking(); - mIwlanDataServiceProvider.close(); + mSpyIwlanDataServiceProvider.close(); + mTestLooper.dispatchAll(); if (mIwlanDataService != null) { mIwlanDataService.onDestroy(); } } - @Test - public void testWifiOnAvailable() { - IwlanNetworkMonitorCallback mNetworkMonitorCallback = - mIwlanDataService.getNetworkMonitorCallback(); + public Network createMockNetwork(LinkProperties linkProperties) { + Network network = mock(Network.class); + when(mMockConnectivityManager.getLinkProperties(eq(network))).thenReturn(linkProperties); + return network; + } + + private NetworkCallback getNetworkMonitorCallback() { + return mNetworkCallbackCaptor.getValue(); + } - mNetworkMonitorCallback.onAvailable(mMockNetwork); - boolean ret = mIwlanDataService.isNetworkConnected(true, false); + private void onSystemDefaultNetworkConnected( + Network network, LinkProperties linkProperties, int transportType, int subId) { + NetworkCapabilities nc = + prepareNetworkCapabilitiesForTest( + transportType, + subId /* unused if transportType is TRANSPORT_WIFI */, + false /* isVcn */); + NetworkCallback networkMonitorCallback = getNetworkMonitorCallback(); + networkMonitorCallback.onCapabilitiesChanged(network, nc); + networkMonitorCallback.onLinkPropertiesChanged(network, linkProperties); + mTestLooper.dispatchAll(); + } + + private void onSystemDefaultNetworkConnected(int transportType) { + Network newNetwork = createMockNetwork(mLinkProperties); + onSystemDefaultNetworkConnected( + newNetwork, mLinkProperties, transportType, DEFAULT_SUB_INDEX); + } - assertTrue(ret); + private void onSystemDefaultNetworkLost() { + NetworkCallback networkMonitorCallback = getNetworkMonitorCallback(); + networkMonitorCallback.onLost(mMockNetwork); + mTestLooper.dispatchAll(); + } + + @Test + public void testWifiOnConnected() { + onSystemDefaultNetworkConnected(TRANSPORT_WIFI); + assertTrue( + mIwlanDataService.isNetworkConnected( + false /* isActiveDataOnOtherSub */, false /* isCstEnabled */)); } @Test public void testWifiOnLost() { + when(mMockIwlanDataServiceProvider.getSlotIndex()).thenReturn(DEFAULT_SLOT_INDEX + 1); mIwlanDataService.addIwlanDataServiceProvider(mMockIwlanDataServiceProvider); - IwlanNetworkMonitorCallback mNetworkMonitorCallback = - mIwlanDataService.getNetworkMonitorCallback(); - mNetworkMonitorCallback.onLost(mMockNetwork); - boolean ret = mIwlanDataService.isNetworkConnected(true, false); - - assertFalse(ret); + onSystemDefaultNetworkLost(); + assertFalse( + mIwlanDataService.isNetworkConnected( + false /* isActiveDataOnOtherSub */, false /* isCstEnabled */)); verify(mMockIwlanDataServiceProvider).forceCloseTunnelsInDeactivatingState(); mIwlanDataService.removeDataServiceProvider(mMockIwlanDataServiceProvider); + mTestLooper.dispatchAll(); + } + + @Test + public void testWifiOnReconnected() { + Network newNetwork = createMockNetwork(mLinkProperties); + onSystemDefaultNetworkConnected( + newNetwork, mLinkProperties, TRANSPORT_WIFI, INVALID_SUB_INDEX); + verify(mMockEpdgTunnelManager, times(1)).updateNetwork(eq(newNetwork), eq(mLinkProperties)); + + onSystemDefaultNetworkLost(); + onSystemDefaultNetworkConnected( + newNetwork, mLinkProperties, TRANSPORT_WIFI, INVALID_SUB_INDEX); + verify(mMockEpdgTunnelManager, times(2)).updateNetwork(eq(newNetwork), eq(mLinkProperties)); + } + + @Test + public void testOnLinkPropertiesChangedForConnectedNetwork() { + NetworkCallback networkCallback = getNetworkMonitorCallback(); + onSystemDefaultNetworkConnected( + mMockNetwork, mLinkProperties, TRANSPORT_WIFI, INVALID_SUB_INDEX); + + clearInvocations(mMockEpdgTunnelManager); + + LinkProperties newLinkProperties = new LinkProperties(mLinkProperties); + newLinkProperties.setInterfaceName("wlan0"); + newLinkProperties.addLinkAddress(mMockIPv6LinkAddress); + + networkCallback.onLinkPropertiesChanged(mMockNetwork, newLinkProperties); + verify(mMockEpdgTunnelManager, times(1)) + .updateNetwork(eq(mMockNetwork), eq(newLinkProperties)); + } + + @Test + public void testOnLinkPropertiesChangedForNonConnectedNetwork() { + NetworkCallback networkCallback = getNetworkMonitorCallback(); + onSystemDefaultNetworkConnected( + mMockNetwork, mLinkProperties, TRANSPORT_WIFI, INVALID_SUB_INDEX); + + clearInvocations(mMockEpdgTunnelManager); + + LinkProperties newLinkProperties = new LinkProperties(); + newLinkProperties.setInterfaceName("wlan0"); + newLinkProperties.addLinkAddress(mMockIPv6LinkAddress); + Network newNetwork = createMockNetwork(newLinkProperties); + + networkCallback.onLinkPropertiesChanged(newNetwork, newLinkProperties); + verify(mMockEpdgTunnelManager, never()) + .updateNetwork(eq(newNetwork), any(LinkProperties.class)); + } + + @Test + public void testOnLinkPropertiesChangedWithClatInstalled() throws Exception { + NetworkCallback networkCallback = getNetworkMonitorCallback(); + mLinkProperties.setLinkAddresses( + new ArrayList<>(Collections.singletonList(mMockIPv6LinkAddress))); + onSystemDefaultNetworkConnected( + mMockNetwork, mLinkProperties, TRANSPORT_WIFI, INVALID_SUB_INDEX); + + clearInvocations(mMockEpdgTunnelManager); + + // LinkProperties#addStackedLink() is marked with @UnsupportedAppUsage + LinkProperties newLinkProperties = new LinkProperties(mLinkProperties); + newLinkProperties.setInterfaceName("wlan0"); + LinkProperties stackedLink = new LinkProperties(); + stackedLink.setInterfaceName("v4-wlan0"); + stackedLink.addLinkAddress(mMockIPv4LinkAddress); + Class<?>[] parameterTypes = new Class<?>[] {LinkProperties.class}; + Object[] args = new Object[] {stackedLink}; + callUnsupportedAppUsageMethod(newLinkProperties, "addStackedLink", parameterTypes, args); + assertNotEquals(mLinkProperties, newLinkProperties); + + networkCallback.onLinkPropertiesChanged(mMockNetwork, newLinkProperties); + verify(mMockEpdgTunnelManager, times(1)) + .updateNetwork(eq(mMockNetwork), eq(newLinkProperties)); + } + + @Test + public void testOnLinkPropertiesChangedForBringingUpIkeSession() { + DataProfile dp = buildImsDataProfile(); + + NetworkCallback networkCallback = getNetworkMonitorCallback(); + onSystemDefaultNetworkConnected( + mMockNetwork, mLinkProperties, TRANSPORT_WIFI, INVALID_SUB_INDEX); + + clearInvocations(mMockEpdgTunnelManager); + + mSpyIwlanDataServiceProvider.setTunnelState( + dp, + mMockDataServiceCallback, + TunnelState.TUNNEL_IN_BRINGUP, + null, /* linkProperties */ + false /* isHandover */, + 1 /* pduSessionId */, + true /* isImsOrEmergency */); + + LinkProperties newLinkProperties = new LinkProperties(mLinkProperties); + newLinkProperties.setInterfaceName("wlan0"); + newLinkProperties.addLinkAddress(mMockIPv6LinkAddress); + + networkCallback.onLinkPropertiesChanged(mMockNetwork, newLinkProperties); + verify(mMockEpdgTunnelManager, times(1)) + .updateNetwork(eq(mMockNetwork), eq(newLinkProperties)); + verify(mMockEpdgTunnelManager, never()).closeTunnel(any(), anyBoolean(), any(), any()); + } + + @Test + public void testNetworkNotConnectedWithCellularOnSameSubAndCrossSimEnabled() + throws InterruptedException { + NetworkCapabilities nc = + prepareNetworkCapabilitiesForTest( + TRANSPORT_CELLULAR, DEFAULT_SUB_INDEX, false /* isVcn */); + getNetworkMonitorCallback().onCapabilitiesChanged(mMockNetwork, nc); + + boolean isActiveDataOnOtherSub = + mIwlanDataService.isActiveDataOnOtherSub(DEFAULT_SLOT_INDEX); + + assertFalse(isActiveDataOnOtherSub); + assertFalse( + mIwlanDataService.isNetworkConnected( + isActiveDataOnOtherSub, true /* isCstEnabled */)); + } + + @Test + public void testCrossSimNetworkConnectedWithCellularOnDifferentSub() + throws InterruptedException { + NetworkCapabilities nc = + prepareNetworkCapabilitiesForTest( + TRANSPORT_CELLULAR, DEFAULT_SUB_INDEX + 1, false /* isVcn */); + getNetworkMonitorCallback().onCapabilitiesChanged(mMockNetwork, nc); + + boolean isActiveDataOnOtherSub = + mIwlanDataService.isActiveDataOnOtherSub(DEFAULT_SLOT_INDEX); + + assertTrue(isActiveDataOnOtherSub); + assertTrue( + mIwlanDataService.isNetworkConnected( + isActiveDataOnOtherSub, true /* isCstEnabled */)); + } + + @Test + public void testCrossSimNetworkConnectedWithVcnCellularOnDifferentSub() + throws InterruptedException { + NetworkCapabilities nc = + prepareNetworkCapabilitiesForTest( + TRANSPORT_CELLULAR, DEFAULT_SUB_INDEX + 1, true /* isVcn */); + getNetworkMonitorCallback().onCapabilitiesChanged(mMockNetwork, nc); + + boolean isActiveDataOnOtherSub = + mIwlanDataService.isActiveDataOnOtherSub(DEFAULT_SLOT_INDEX); + + assertTrue(isActiveDataOnOtherSub); + assertTrue( + mIwlanDataService.isNetworkConnected( + isActiveDataOnOtherSub, true /* isCstEnabled */)); + } + + @Test + public void testOnCrossSimCallingEnable_doNotUpdateTunnelManagerIfCellularDataOnSameSub() + throws Exception { + when(mMockImsMmTelManager.isCrossSimCallingEnabled()).thenReturn(true); + + Network newNetwork = createMockNetwork(mLinkProperties); + onSystemDefaultNetworkConnected( + newNetwork, mLinkProperties, TRANSPORT_CELLULAR, DEFAULT_SUB_INDEX); + + mIwlanDataService + .mIwlanDataServiceHandler + .obtainMessage( + IwlanEventListener.CROSS_SIM_CALLING_ENABLE_EVENT, + DEFAULT_SLOT_INDEX, + 0 /* unused */) + .sendToTarget(); + mTestLooper.dispatchAll(); + verify(mMockEpdgTunnelManager, never()) + .updateNetwork(eq(newNetwork), any(LinkProperties.class)); + } + + @Test + public void testOnCrossSimCallingEnable_updateTunnelManagerIfCellularDataOnDifferentSub() + throws Exception { + when(mMockImsMmTelManager.isCrossSimCallingEnabled()).thenReturn(true); + + Network newNetwork = createMockNetwork(mLinkProperties); + onSystemDefaultNetworkConnected( + newNetwork, mLinkProperties, TRANSPORT_CELLULAR, DEFAULT_SUB_INDEX + 1); + verify(mMockEpdgTunnelManager, times(1)).updateNetwork(eq(newNetwork), eq(mLinkProperties)); + + mIwlanDataService + .mIwlanDataServiceHandler + .obtainMessage( + IwlanEventListener.CROSS_SIM_CALLING_ENABLE_EVENT, + DEFAULT_SLOT_INDEX, + 0 /* unused */) + .sendToTarget(); + mTestLooper.dispatchAll(); + verify(mMockEpdgTunnelManager, times(2)).updateNetwork(eq(newNetwork), eq(mLinkProperties)); + } + + @Test + public void testOnCrossSimCallingEnable_doNotUpdateTunnelManagerIfNoNetwork() throws Exception { + when(mMockImsMmTelManager.isCrossSimCallingEnabled()).thenReturn(true); + onSystemDefaultNetworkLost(); + + mIwlanDataService + .mIwlanDataServiceHandler + .obtainMessage( + IwlanEventListener.CROSS_SIM_CALLING_ENABLE_EVENT, + DEFAULT_SLOT_INDEX, + 0 /* unused */) + .sendToTarget(); + mTestLooper.dispatchAll(); + verify(mMockEpdgTunnelManager, never()) + .updateNetwork(any(Network.class), any(LinkProperties.class)); + } + + @Test + public void testOnEthernetConnection_doNotUpdateTunnelManager() throws Exception { + Network newNetwork = createMockNetwork(mLinkProperties); + onSystemDefaultNetworkConnected( + newNetwork, mLinkProperties, TRANSPORT_ETHERNET, DEFAULT_SUB_INDEX); + verify(mMockEpdgTunnelManager, never()) + .updateNetwork(eq(newNetwork), any(LinkProperties.class)); + } + + @Test + public void testAddDuplicateDataServiceProviderThrows() throws Exception { + when(mMockIwlanDataServiceProvider.getSlotIndex()).thenReturn(DEFAULT_SLOT_INDEX); + assertThrows( + IllegalStateException.class, + () -> mIwlanDataService.addIwlanDataServiceProvider(mMockIwlanDataServiceProvider)); + } + + @Test + public void testRemoveDataServiceProvider() { + when(mMockIwlanDataServiceProvider.getSlotIndex()).thenReturn(DEFAULT_SLOT_INDEX); + mIwlanDataService.removeDataServiceProvider(mMockIwlanDataServiceProvider); + mTestLooper.dispatchAll(); + verify(mIwlanDataService, times(1)).deinitNetworkCallback(); + mIwlanDataService.onCreateDataServiceProvider(DEFAULT_SLOT_INDEX); + mTestLooper.dispatchAll(); } @Test public void testRequestDataCallListPass() throws Exception { - DataProfile dp = buildDataProfile(); + DataProfile dp = buildImsDataProfile(); List<LinkAddress> mInternalAddressList; List<InetAddress> mDNSAddressList; List<InetAddress> mGatewayAddressList; @@ -267,14 +591,16 @@ public class IwlanDataServiceTest { IwlanDataServiceCallback callback = new IwlanDataServiceCallback("requestDataCallList"); TunnelLinkProperties mLinkProperties = TunnelLinkPropertiesTest.createTestTunnelLinkProperties(); - mIwlanDataServiceProvider.setTunnelState( + mSpyIwlanDataServiceProvider.setTunnelState( dp, new DataServiceCallback(callback), TunnelState.TUNNEL_UP, mLinkProperties, - false, - 1); - mIwlanDataServiceProvider.requestDataCallList(new DataServiceCallback(callback)); + false, /* isHandover */ + 1, /* pduSessionId */ + true /* isImsOrEmergency */); + mSpyIwlanDataServiceProvider.requestDataCallList(new DataServiceCallback(callback)); + mTestLooper.dispatchAll(); latch.await(1, TimeUnit.SECONDS); assertEquals(mResultCode, DataServiceCallback.RESULT_SUCCESS); @@ -318,7 +644,8 @@ public class IwlanDataServiceTest { public void testRequestDataCallListEmpty() throws Exception { latch = new CountDownLatch(1); IwlanDataServiceCallback callback = new IwlanDataServiceCallback("requestDataCallList"); - mIwlanDataServiceProvider.requestDataCallList(new DataServiceCallback(callback)); + mSpyIwlanDataServiceProvider.requestDataCallList(new DataServiceCallback(callback)); + mTestLooper.dispatchAll(); latch.await(1, TimeUnit.SECONDS); assertEquals(mResultCode, DataServiceCallback.RESULT_SUCCESS); @@ -327,7 +654,7 @@ public class IwlanDataServiceTest { @Test public void testIwlanSetupDataCallWithInvalidArg() { - mIwlanDataServiceProvider.setupDataCall( + mSpyIwlanDataServiceProvider.setupDataCall( AccessNetworkType.UNKNOWN, /* AccessNetworkType */ null, /* dataProfile */ false, /* isRoaming */ @@ -339,6 +666,7 @@ public class IwlanDataServiceTest { null, /* trafficDescriptor */ true, /* matchAllRuleAllowed */ mMockDataServiceCallback); + mTestLooper.dispatchAll(); verify(mMockDataServiceCallback, timeout(1000).times(1)) .onSetupDataCallComplete( @@ -347,13 +675,12 @@ public class IwlanDataServiceTest { @Test public void testIwlanSetupDataCallWithIllegalState() { - DataProfile dp = buildDataProfile(); + DataProfile dp = buildImsDataProfile(); /* Wifi is not connected */ - mIwlanDataService.setNetworkConnected( - false, mMockNetwork, IwlanDataService.Transport.UNSPECIFIED_NETWORK); + onSystemDefaultNetworkLost(); - mIwlanDataServiceProvider.setupDataCall( + mSpyIwlanDataServiceProvider.setupDataCall( AccessNetworkType.IWLAN, /* AccessNetworkType */ dp, /* dataProfile */ false, /* isRoaming */ @@ -365,18 +692,21 @@ public class IwlanDataServiceTest { null, /* trafficDescriptor */ true, /* matchAllRuleAllowed */ mMockDataServiceCallback); + mTestLooper.dispatchAll(); verify(mMockDataServiceCallback, timeout(1000).times(1)) .onSetupDataCallComplete( - eq(5 /*DataServiceCallback.RESULT_ERROR_TEMPORARILY_UNAVAILABLE */), isNull()); + eq(5 /*DataServiceCallback.RESULT_ERROR_TEMPORARILY_UNAVAILABLE */), + isNull()); } @Test public void testIwlanDeactivateDataCallWithInvalidArg() { - mIwlanDataServiceProvider.deactivateDataCall( + mSpyIwlanDataServiceProvider.deactivateDataCall( 0, /* cid */ DataService.REQUEST_REASON_NORMAL, /* DataService.REQUEST_REASON_NORMAL */ mMockDataServiceCallback); + mTestLooper.dispatchAll(); verify(mMockDataServiceCallback, timeout(1000).times(1)) .onDeactivateDataCallComplete(eq(DataServiceCallback.RESULT_ERROR_INVALID_ARG)); @@ -384,12 +714,11 @@ public class IwlanDataServiceTest { @Test public void testIwlanSetupDataCallWithBringUpTunnel() { - DataProfile dp = buildDataProfile(); + DataProfile dp = buildImsDataProfile(); /* Wifi is connected */ - mIwlanDataService.setNetworkConnected(true, mMockNetwork, IwlanDataService.Transport.WIFI); - - doReturn(mMockEpdgTunnelManager).when(mSpyIwlanDataServiceProvider).getTunnelManager(); + onSystemDefaultNetworkConnected( + mMockNetwork, mLinkProperties, TRANSPORT_WIFI, INVALID_SUB_INDEX); mSpyIwlanDataServiceProvider.setupDataCall( AccessNetworkType.IWLAN, /* AccessNetworkType */ @@ -403,15 +732,20 @@ public class IwlanDataServiceTest { null, /* trafficDescriptor */ true, /* matchAllRuleAllowed */ mMockDataServiceCallback); + mTestLooper.dispatchAll(); /* Check bringUpTunnel() is called. */ verify(mMockEpdgTunnelManager, times(1)) - .bringUpTunnel(any(TunnelSetupRequest.class), any(IwlanTunnelCallback.class)); + .bringUpTunnel( + any(TunnelSetupRequest.class), + any(IwlanTunnelCallback.class), + any(IwlanTunnelMetricsImpl.class)); /* Check callback result is RESULT_SUCCESS when onOpened() is called. */ mSpyIwlanDataServiceProvider .getIwlanTunnelCallback() .onOpened(TEST_APN_NAME, mMockTunnelLinkProperties); + mTestLooper.dispatchAll(); verify(mMockDataServiceCallback, times(1)) .onSetupDataCallComplete( eq(DataServiceCallback.RESULT_SUCCESS), any(DataCallResponse.class)); @@ -419,12 +753,11 @@ public class IwlanDataServiceTest { @Test public void testSliceInfoInclusionInDataCallResponse() throws Exception { - DataProfile dp = buildDataProfile(); + DataProfile dp = buildImsDataProfile(); /* Wifi is connected */ - mIwlanDataService.setNetworkConnected(true, mMockNetwork, IwlanDataService.Transport.WIFI); - - doReturn(mMockEpdgTunnelManager).when(mSpyIwlanDataServiceProvider).getTunnelManager(); + onSystemDefaultNetworkConnected( + mMockNetwork, mLinkProperties, TRANSPORT_WIFI, INVALID_SUB_INDEX); mSpyIwlanDataServiceProvider.setupDataCall( AccessNetworkType.IWLAN, /* AccessNetworkType */ @@ -438,10 +771,14 @@ public class IwlanDataServiceTest { null, /* trafficDescriptor */ true, /* matchAllRuleAllowed */ mMockDataServiceCallback); + mTestLooper.dispatchAll(); /* Check bringUpTunnel() is called. */ verify(mMockEpdgTunnelManager, times(1)) - .bringUpTunnel(any(TunnelSetupRequest.class), any(IwlanTunnelCallback.class)); + .bringUpTunnel( + any(TunnelSetupRequest.class), + any(IwlanTunnelCallback.class), + any(IwlanTunnelMetricsImpl.class)); /* Check callback result is RESULT_SUCCESS when onOpened() is called. */ TunnelLinkProperties tp = TunnelLinkPropertiesTest.createTestTunnelLinkProperties(); @@ -450,6 +787,7 @@ public class IwlanDataServiceTest { ArgumentCaptor.forClass(DataCallResponse.class); mSpyIwlanDataServiceProvider.getIwlanTunnelCallback().onOpened(TEST_APN_NAME, tp); + mTestLooper.dispatchAll(); verify(mMockDataServiceCallback, times(1)) .onSetupDataCallComplete( eq(DataServiceCallback.RESULT_SUCCESS), dataCallResponseCaptor.capture()); @@ -462,32 +800,81 @@ public class IwlanDataServiceTest { @Test public void testIwlanDeactivateDataCallWithCloseTunnel() { - DataProfile dp = buildDataProfile(); + DataProfile dp = buildImsDataProfile(); - doReturn(mMockEpdgTunnelManager).when(mSpyIwlanDataServiceProvider).getTunnelManager(); + onSystemDefaultNetworkConnected(TRANSPORT_WIFI); mSpyIwlanDataServiceProvider.setTunnelState( - dp, mMockDataServiceCallback, TunnelState.TUNNEL_IN_BRINGUP, null, false, 1); + dp, + mMockDataServiceCallback, + TunnelState.TUNNEL_IN_BRINGUP, + null, /* linkProperties */ + false, /* isHandover */ + 1, /* pduSessionId */ + true /* isImsOrEmergency */); mSpyIwlanDataServiceProvider.deactivateDataCall( TEST_APN_NAME.hashCode() /* cid: hashcode() of "ims" */, - DataService.REQUEST_REASON_NORMAL /* DataService.REQUEST_REASON_NORMAL */, + DataService.REQUEST_REASON_NORMAL, mMockDataServiceCallback); + mTestLooper.dispatchAll(); + /* Check closeTunnel() is called. */ + verify(mMockEpdgTunnelManager, times(1)) + .closeTunnel( + eq(TEST_APN_NAME), + eq(false), + any(IwlanTunnelCallback.class), + any(IwlanTunnelMetricsImpl.class)); + /* Check callback result is RESULT_SUCCESS when onClosed() is called. */ + mSpyIwlanDataServiceProvider + .getIwlanTunnelCallback() + .onClosed(TEST_APN_NAME, new IwlanError(IwlanError.NO_ERROR)); + mTestLooper.dispatchAll(); + verify(mMockDataServiceCallback, times(1)) + .onDeactivateDataCallComplete(eq(DataServiceCallback.RESULT_SUCCESS)); + } + + @Test + public void testIwlanDeactivateDataCallAfterSuccessHandover() { + DataProfile dp = buildImsDataProfile(); + + onSystemDefaultNetworkConnected(TRANSPORT_WIFI); + + mSpyIwlanDataServiceProvider.setTunnelState( + dp, + mMockDataServiceCallback, + TunnelState.TUNNEL_IN_BRINGUP, + null, /* linkProperties */ + false, /* isHandover */ + 1, /* pduSessionId */ + true /* isImsOrEmergency */); + + mSpyIwlanDataServiceProvider.deactivateDataCall( + TEST_APN_NAME.hashCode() /* cid: hashcode() of "ims" */, + DataService.REQUEST_REASON_HANDOVER, + mMockDataServiceCallback); + mTestLooper.dispatchAll(); /* Check closeTunnel() is called. */ - verify(mMockEpdgTunnelManager, times(1)).closeTunnel(eq(TEST_APN_NAME), anyBoolean()); + verify(mMockEpdgTunnelManager, times(1)) + .closeTunnel( + eq(TEST_APN_NAME), + eq(true), + any(IwlanTunnelCallback.class), + any(IwlanTunnelMetricsImpl.class)); /* Check callback result is RESULT_SUCCESS when onClosed() is called. */ mSpyIwlanDataServiceProvider .getIwlanTunnelCallback() .onClosed(TEST_APN_NAME, new IwlanError(IwlanError.NO_ERROR)); + mTestLooper.dispatchAll(); verify(mMockDataServiceCallback, times(1)) .onDeactivateDataCallComplete(eq(DataServiceCallback.RESULT_SUCCESS)); } @Test - public void testHandoverFailureModeNormal() { - DataProfile dp = buildDataProfile(); + public void testHandoverFailureModeDefault() { + DataProfile dp = buildImsDataProfile(); int setupDataReason = DataService.REQUEST_REASON_NORMAL; when(ErrorPolicyManager.getInstance(eq(mMockContext), eq(DEFAULT_SLOT_INDEX))) @@ -500,13 +887,25 @@ public class IwlanDataServiceTest { dp, mMockDataServiceCallback, TunnelState.TUNNEL_IN_BRINGUP, - null, + null, /* linkProperties */ (setupDataReason == DataService.REQUEST_REASON_HANDOVER), - 1); + 1 /* pduSessionId */, + true /* isImsOrEmergency */); + + mSpyIwlanDataServiceProvider.setMetricsAtom( + TEST_APN_NAME, + 64, // type IMS + true, + 13, // LTE + false, + true, + 1 // Transport Wi-Fi + ); mSpyIwlanDataServiceProvider .getIwlanTunnelCallback() .onClosed(TEST_APN_NAME, new IwlanError(IwlanError.NO_ERROR)); + mTestLooper.dispatchAll(); ArgumentCaptor<DataCallResponse> dataCallResponseCaptor = ArgumentCaptor.forClass(DataCallResponse.class); @@ -518,14 +917,14 @@ public class IwlanDataServiceTest { DataCallResponse dataCallResponse = dataCallResponseCaptor.getValue(); assertEquals( dataCallResponse.getHandoverFailureMode(), - DataCallResponse.HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL); + DataCallResponse.HANDOVER_FAILURE_MODE_LEGACY); assertEquals(dataCallResponse.getCause(), DataFailCause.USER_AUTHENTICATION); assertEquals(dataCallResponse.getRetryDurationMillis(), 5L); } @Test public void testHandoverFailureModeHandover() { - DataProfile dp = buildDataProfile(); + DataProfile dp = buildImsDataProfile(); int setupDataReason = DataService.REQUEST_REASON_HANDOVER; when(ErrorPolicyManager.getInstance(eq(mMockContext), eq(DEFAULT_SLOT_INDEX))) @@ -533,18 +932,32 @@ public class IwlanDataServiceTest { when(mMockErrorPolicyManager.getCurrentRetryTimeMs(eq(TEST_APN_NAME))).thenReturn(-1L); when(mMockErrorPolicyManager.getDataFailCause(eq(TEST_APN_NAME))) .thenReturn(DataFailCause.ERROR_UNSPECIFIED); + when(mMockErrorPolicyManager.shouldRetryWithInitialAttach(eq(TEST_APN_NAME))) + .thenReturn(false); mSpyIwlanDataServiceProvider.setTunnelState( dp, mMockDataServiceCallback, TunnelState.TUNNEL_IN_BRINGUP, - null, + null, /* linkProperties */ (setupDataReason == DataService.REQUEST_REASON_HANDOVER), - 1); + 1 /* pduSessionId */, + true /* isImsOrEmergency */); + + mSpyIwlanDataServiceProvider.setMetricsAtom( + TEST_APN_NAME, + 64, // type IMS + true, + 13, // LTE + false, + true, + 1 // Transport Wi-Fi + ); mSpyIwlanDataServiceProvider .getIwlanTunnelCallback() .onClosed(TEST_APN_NAME, new IwlanError(IwlanError.NO_ERROR)); + mTestLooper.dispatchAll(); ArgumentCaptor<DataCallResponse> dataCallResponseCaptor = ArgumentCaptor.forClass(DataCallResponse.class); @@ -562,34 +975,268 @@ public class IwlanDataServiceTest { } @Test - public void testDnsPrefetching() throws Exception { - IwlanNetworkMonitorCallback mNetworkMonitorCallback = - mIwlanDataService.getNetworkMonitorCallback(); - /* Wifi is connected */ - mIwlanDataService.setNetworkConnected(true, mMockNetwork, IwlanDataService.Transport.WIFI); + public void testSupportInitialAttachSuccessOnIms() { + DataProfile dp = buildImsDataProfile(); + int setupDataReason = DataService.REQUEST_REASON_HANDOVER; + + when(ErrorPolicyManager.getInstance(eq(mMockContext), eq(DEFAULT_SLOT_INDEX))) + .thenReturn(mMockErrorPolicyManager); + when(mMockErrorPolicyManager.getCurrentRetryTimeMs(eq(TEST_APN_NAME))).thenReturn(-1L); + when(mMockErrorPolicyManager.getDataFailCause(eq(TEST_APN_NAME))) + .thenReturn(DataFailCause.ERROR_UNSPECIFIED); + when(mMockErrorPolicyManager.shouldRetryWithInitialAttach(eq(TEST_APN_NAME))) + .thenReturn(true); + + // APN = IMS, in idle call state + mIwlanDataService + .mIwlanDataServiceHandler + .obtainMessage( + IwlanEventListener.CALL_STATE_CHANGED_EVENT, + DEFAULT_SLOT_INDEX, + TelephonyManager.CALL_STATE_IDLE) + .sendToTarget(); + + mSpyIwlanDataServiceProvider.setTunnelState( + dp, + mMockDataServiceCallback, + TunnelState.TUNNEL_IN_BRINGUP, + null, /* linkProperties */ + (setupDataReason == DataService.REQUEST_REASON_HANDOVER), + 1 /* pduSessionId */, + true /* isImsOrEmergency */); + + mSpyIwlanDataServiceProvider.setMetricsAtom( + TEST_APN_NAME, + 64, // type IMS + true, + 13, // LTE + false, + true, + 1 // Transport Wi-Fi + ); - List<LinkAddress> linkAddresses = new ArrayList<>(); - linkAddresses.add(mMockIPv4LinkAddress); + mSpyIwlanDataServiceProvider + .getIwlanTunnelCallback() + .onClosed(TEST_APN_NAME, new IwlanError(IwlanError.NO_ERROR)); + mTestLooper.dispatchAll(); - when(mMockLinkProperties.getLinkAddresses()).thenReturn(linkAddresses); - mNetworkMonitorCallback.onLinkPropertiesChanged(mMockNetwork, mMockLinkProperties); + ArgumentCaptor<DataCallResponse> dataCallResponseCaptor = + ArgumentCaptor.forClass(DataCallResponse.class); + verify(mMockDataServiceCallback, times(1)) + .onSetupDataCallComplete( + eq(DataServiceCallback.RESULT_SUCCESS), dataCallResponseCaptor.capture()); + DataCallResponse dataCallResponse = dataCallResponseCaptor.getValue(); + // Not on video or voice call + assertEquals( + DataCallResponse.HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL, + dataCallResponse.getHandoverFailureMode()); + } - mIwlanDataServiceProvider - .mHandler - .obtainMessage(IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT) + @Test + public void testSupportInitialAttachSuccessOnEmergency() { + DataProfile dp = buildDataProfile(ApnSetting.TYPE_EMERGENCY); + int setupDataReason = DataService.REQUEST_REASON_HANDOVER; + + when(ErrorPolicyManager.getInstance(eq(mMockContext), eq(DEFAULT_SLOT_INDEX))) + .thenReturn(mMockErrorPolicyManager); + when(mMockErrorPolicyManager.getCurrentRetryTimeMs(eq(TEST_APN_NAME))).thenReturn(-1L); + when(mMockErrorPolicyManager.getDataFailCause(eq(TEST_APN_NAME))) + .thenReturn(DataFailCause.ERROR_UNSPECIFIED); + when(mMockErrorPolicyManager.shouldRetryWithInitialAttach(eq(TEST_APN_NAME))) + .thenReturn(true); + + // APN = Emergency, in idle call state + mIwlanDataService + .mIwlanDataServiceHandler + .obtainMessage( + IwlanEventListener.CALL_STATE_CHANGED_EVENT, + DEFAULT_SLOT_INDEX, + TelephonyManager.CALL_STATE_IDLE) .sendToTarget(); - sleep(1000); - mIwlanDataServiceProvider - .mHandler - .obtainMessage(IwlanEventListener.WIFI_CALLING_ENABLE_EVENT) + mSpyIwlanDataServiceProvider.setTunnelState( + dp, + mMockDataServiceCallback, + TunnelState.TUNNEL_IN_BRINGUP, + null, /* linkProperties */ + (setupDataReason == DataService.REQUEST_REASON_HANDOVER), + 1 /* pduSessionId */, + true /* isImsOrEmergency */); + + mSpyIwlanDataServiceProvider.setMetricsAtom( + TEST_APN_NAME, + 512, // type Emergency + true, + 13, // LTE + false, + true, + 1 // Transport Wi-Fi + ); + + mSpyIwlanDataServiceProvider + .getIwlanTunnelCallback() + .onClosed(TEST_APN_NAME, new IwlanError(IwlanError.NO_ERROR)); + mTestLooper.dispatchAll(); + + ArgumentCaptor<DataCallResponse> dataCallResponseCaptor = + ArgumentCaptor.forClass(DataCallResponse.class); + verify(mMockDataServiceCallback, times(1)) + .onSetupDataCallComplete( + eq(DataServiceCallback.RESULT_SUCCESS), dataCallResponseCaptor.capture()); + DataCallResponse dataCallResponse = dataCallResponseCaptor.getValue(); + // Not on video or voice call + assertEquals( + DataCallResponse.HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL, + dataCallResponse.getHandoverFailureMode()); + } + + @Test + public void testSupportInitialAttachOnImsCall() { + DataProfile dp = buildImsDataProfile(); + int setupDataReason = DataService.REQUEST_REASON_HANDOVER; + + when(ErrorPolicyManager.getInstance(eq(mMockContext), eq(DEFAULT_SLOT_INDEX))) + .thenReturn(mMockErrorPolicyManager); + when(mMockErrorPolicyManager.getCurrentRetryTimeMs(eq(TEST_APN_NAME))).thenReturn(-1L); + when(mMockErrorPolicyManager.getDataFailCause(eq(TEST_APN_NAME))) + .thenReturn(DataFailCause.ERROR_UNSPECIFIED); + when(mMockErrorPolicyManager.shouldRetryWithInitialAttach(eq(TEST_APN_NAME))) + .thenReturn(true); + + // APN = IMS, in call + mIwlanDataService + .mIwlanDataServiceHandler + .obtainMessage( + IwlanEventListener.CALL_STATE_CHANGED_EVENT, + DEFAULT_SLOT_INDEX, + TelephonyManager.CALL_STATE_OFFHOOK) + .sendToTarget(); + + mSpyIwlanDataServiceProvider.setTunnelState( + dp, + mMockDataServiceCallback, + TunnelState.TUNNEL_IN_BRINGUP, + null /* linkProperties */, + (setupDataReason == DataService.REQUEST_REASON_HANDOVER), + 1 /* pduSessionId */, + true /* isImsOrEmergency */); + + mSpyIwlanDataServiceProvider.setMetricsAtom( + TEST_APN_NAME, + 64, // type IMS + true, + 13, // LTE + false, + true, + 1 // Transport Wi-Fi + ); + + mSpyIwlanDataServiceProvider + .getIwlanTunnelCallback() + .onClosed(TEST_APN_NAME, new IwlanError(IwlanError.NO_ERROR)); + mTestLooper.dispatchAll(); + + ArgumentCaptor<DataCallResponse> dataCallResponseCaptor = + ArgumentCaptor.forClass(DataCallResponse.class); + verify(mMockDataServiceCallback, times(1)) + .onSetupDataCallComplete( + eq(DataServiceCallback.RESULT_SUCCESS), dataCallResponseCaptor.capture()); + DataCallResponse dataCallResponse = dataCallResponseCaptor.getValue(); + // In call state + assertEquals( + DataCallResponse.HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER, + dataCallResponse.getHandoverFailureMode()); + } + + @Test + public void testSupportInitialAttachOnEmergencyCall() { + DataProfile dp = buildDataProfile(ApnSetting.TYPE_EMERGENCY); + int setupDataReason = DataService.REQUEST_REASON_HANDOVER; + + when(ErrorPolicyManager.getInstance(eq(mMockContext), eq(DEFAULT_SLOT_INDEX))) + .thenReturn(mMockErrorPolicyManager); + when(mMockErrorPolicyManager.getCurrentRetryTimeMs(eq(TEST_APN_NAME))).thenReturn(-1L); + when(mMockErrorPolicyManager.getDataFailCause(eq(TEST_APN_NAME))) + .thenReturn(DataFailCause.ERROR_UNSPECIFIED); + when(mMockErrorPolicyManager.shouldRetryWithInitialAttach(eq(TEST_APN_NAME))) + .thenReturn(true); + + // APN = Emergency, in call + mIwlanDataService + .mIwlanDataServiceHandler + .obtainMessage( + IwlanEventListener.CALL_STATE_CHANGED_EVENT, + DEFAULT_SLOT_INDEX, + TelephonyManager.CALL_STATE_OFFHOOK) + .sendToTarget(); + + mSpyIwlanDataServiceProvider.setTunnelState( + dp, + mMockDataServiceCallback, + TunnelState.TUNNEL_IN_BRINGUP, + null /* linkProperties */, + (setupDataReason == DataService.REQUEST_REASON_HANDOVER), + 1 /* pduSessionId */, + true /* isImsOrEmergency */); + + mSpyIwlanDataServiceProvider.setMetricsAtom( + TEST_APN_NAME, + 512, // type Emergency + true, + 13, // LTE + false, + true, + 1 // Transport Wi-Fi + ); + + mSpyIwlanDataServiceProvider + .getIwlanTunnelCallback() + .onClosed(TEST_APN_NAME, new IwlanError(IwlanError.NO_ERROR)); + mTestLooper.dispatchAll(); + + ArgumentCaptor<DataCallResponse> dataCallResponseCaptor = + ArgumentCaptor.forClass(DataCallResponse.class); + verify(mMockDataServiceCallback, times(1)) + .onSetupDataCallComplete( + eq(DataServiceCallback.RESULT_SUCCESS), dataCallResponseCaptor.capture()); + DataCallResponse dataCallResponse = dataCallResponseCaptor.getValue(); + // In call state + assertEquals( + DataCallResponse.HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER, + dataCallResponse.getHandoverFailureMode()); + } + + @Test + public void testDnsPrefetching() throws Exception { + NetworkCallback networkCallback = getNetworkMonitorCallback(); + /* Wifi is connected */ + onSystemDefaultNetworkConnected( + mMockNetwork, mLinkProperties, TRANSPORT_WIFI, INVALID_SUB_INDEX); + networkCallback.onLinkPropertiesChanged(mMockNetwork, mLinkProperties); + + mIwlanDataService + .mIwlanDataServiceHandler + .obtainMessage( + IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT, + DEFAULT_SLOT_INDEX, + 0 /* unused */) + .sendToTarget(); + + mIwlanDataService + .mIwlanDataServiceHandler + .obtainMessage( + IwlanEventListener.WIFI_CALLING_ENABLE_EVENT, + DEFAULT_SLOT_INDEX, + 0 /* unused */) .sendToTarget(); - sleep(1000); + mTestLooper.dispatchAll(); - linkAddresses.add(mMockIPv6LinkAddress); + LinkProperties newLinkProperties = new LinkProperties(); + newLinkProperties.setInterfaceName("wlan0"); + newLinkProperties.addLinkAddress(mMockIPv4LinkAddress); + newLinkProperties.addLinkAddress(mMockIPv6LinkAddress); - when(mMockLinkProperties.getLinkAddresses()).thenReturn(linkAddresses); - mNetworkMonitorCallback.onLinkPropertiesChanged(mMockNetwork, mMockLinkProperties); + networkCallback.onLinkPropertiesChanged(mMockNetwork, newLinkProperties); /* Prefetching will be triggered twice. 1. Network connected, CarrierConfig ready, WifiCallingSetting enabled @@ -599,6 +1246,7 @@ public class IwlanDataServiceTest { .getValidatedServerList( eq(0), eq(EpdgSelector.PROTO_FILTER_IPV4V6), + eq(EpdgSelector.SYSTEM_PREFERRED), eq(false), eq(false), eq(mMockNetwork), @@ -607,21 +1255,26 @@ public class IwlanDataServiceTest { .getValidatedServerList( eq(0), eq(EpdgSelector.PROTO_FILTER_IPV4V6), + eq(EpdgSelector.SYSTEM_PREFERRED), eq(false), eq(true), eq(mMockNetwork), isNull()); } - private void sleep(long time) { - try { - Thread.sleep(time); - } catch (Exception e) { - e.printStackTrace(); + private void advanceCalendarByTimeMs(long time, Calendar calendar) { + mMockedCalendarTime += time; + if (calendar != null) { + calendar.setTimeInMillis(mMockedCalendarTime); } + mTestLooper.dispatchAll(); } - private DataProfile buildDataProfile() { + private DataProfile buildImsDataProfile() { + return buildDataProfile(ApnSetting.TYPE_IMS); + } + + private DataProfile buildDataProfile(int supportedApnTypesBitmask) { DataProfile dp = new DataProfile.Builder() .setProfileId(1) @@ -635,7 +1288,7 @@ public class IwlanDataServiceTest { // .setMaxConnections(3) // .setWaitTime(10) .enable(true) - .setSupportedApnTypesBitmask(ApnSetting.TYPE_IMS) + .setSupportedApnTypesBitmask(supportedApnTypesBitmask) .setRoamingProtocolType(ApnSetting.PROTOCOL_IPV4V6) // IPv4v6 .setBearerBitmask((int) TelephonyManager.NETWORK_TYPE_BITMASK_IWLAN) .setPersistent(true) @@ -644,24 +1297,30 @@ public class IwlanDataServiceTest { return dp; } - @Test - public void testIwlanSetupDataCallWithCellularAndCstDisabled() { - DataProfile dp = buildDataProfile(); + private NetworkCapabilities prepareNetworkCapabilitiesForTest( + int transportType, int subId, boolean isVcn) { + NetworkCapabilities.Builder builder = + new NetworkCapabilities.Builder().addTransportType(transportType); + if (isVcn) { + builder.setTransportInfo(new VcnTransportInfo(subId)); + } else { + builder.setNetworkSpecifier(new TelephonyNetworkSpecifier(subId)); + } + return builder.build(); + } - /* Mobile is connected */ - mIwlanDataService.setNetworkConnected( - true, mMockNetwork, IwlanDataService.Transport.MOBILE); + @Test + public void testIwlanSetupDataCallFailsWithCellularAndCstDisabled() throws Exception { + DataProfile dp = buildImsDataProfile(); + /* CST is disabled, and data is on the same sub as the data service provider */ + when(mMockImsMmTelManager.isCrossSimCallingEnabled()).thenReturn(false); - lenient() - .when( - IwlanHelper.isCrossSimCallingEnabled( - eq(mMockContext), eq(DEFAULT_SLOT_INDEX))) - .thenReturn(false); - lenient() - .when(IwlanHelper.isDefaultDataSlot(eq(mMockContext), eq(DEFAULT_SLOT_INDEX))) - .thenReturn(true); + NetworkCapabilities nc = + prepareNetworkCapabilitiesForTest( + TRANSPORT_CELLULAR, DEFAULT_SUB_INDEX, false /* isVcn */); + getNetworkMonitorCallback().onCapabilitiesChanged(mMockNetwork, nc); - mIwlanDataServiceProvider.setupDataCall( + mSpyIwlanDataServiceProvider.setupDataCall( AccessNetworkType.IWLAN, /* AccessNetworkType */ dp, /* dataProfile */ false, /* isRoaming */ @@ -673,34 +1332,58 @@ public class IwlanDataServiceTest { null, /* trafficDescriptor */ true, /* matchAllRuleAllowed */ mMockDataServiceCallback); + mTestLooper.dispatchAll(); verify(mMockDataServiceCallback, timeout(1000).times(1)) .onSetupDataCallComplete( - eq(5 /* DataServiceCallback.RESULT_ERROR_TEMPORARILY_UNAVAILABLE */), isNull()); + eq(5 /* DataServiceCallback.RESULT_ERROR_TEMPORARILY_UNAVAILABLE */), + isNull()); } @Test - public void testIwlanSetupDataCallWithCellularAndCstEnabled() { - DataProfile dp = buildDataProfile(); + public void testIwlanSetupDataCallFailsWithCellularOnSameSubAndCstEnabled() throws Exception { + DataProfile dp = buildImsDataProfile(); - /* Clear state */ - mIwlanDataService.setNetworkConnected( - false, mMockNetwork, IwlanDataService.Transport.UNSPECIFIED_NETWORK); + /* CST is enabled, but data is on the same sub as the DataServiceProvider */ + when(mMockImsMmTelManager.isCrossSimCallingEnabled()).thenReturn(true); - /* Mobile is connected */ - mIwlanDataService.setNetworkConnected( - true, mMockNetwork, IwlanDataService.Transport.MOBILE); + NetworkCapabilities nc = + prepareNetworkCapabilitiesForTest( + TRANSPORT_CELLULAR, DEFAULT_SUB_INDEX, false /* isVcn */); + getNetworkMonitorCallback().onCapabilitiesChanged(mMockNetwork, nc); - doReturn(mMockEpdgTunnelManager).when(mSpyIwlanDataServiceProvider).getTunnelManager(); + 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(); - lenient() - .when( - IwlanHelper.isCrossSimCallingEnabled( - eq(mMockContext), eq(DEFAULT_SLOT_INDEX))) - .thenReturn(true); - lenient() - .when(IwlanHelper.isDefaultDataSlot(eq(mMockContext), eq(DEFAULT_SLOT_INDEX))) - .thenReturn(false); + verify(mMockDataServiceCallback, timeout(1000).times(1)) + .onSetupDataCallComplete( + eq(5 /* DataServiceCallback.RESULT_ERROR_TEMPORARILY_UNAVAILABLE */), + isNull()); + } + + @Test + public void testIwlanSetupDataCallSucceedsWithCellularOnDifferentSubAndCstEnabled() + throws Exception { + DataProfile dp = buildImsDataProfile(); + + /* CST is enabled, but data is on the same sub as the DataServiceProvider */ + when(mMockImsMmTelManager.isCrossSimCallingEnabled()).thenReturn(true); + + NetworkCapabilities nc = + prepareNetworkCapabilitiesForTest( + TRANSPORT_CELLULAR, DEFAULT_SUB_INDEX + 1, false /* isVcn */); + getNetworkMonitorCallback().onCapabilitiesChanged(mMockNetwork, nc); mSpyIwlanDataServiceProvider.setupDataCall( AccessNetworkType.IWLAN, /* AccessNetworkType */ @@ -714,15 +1397,20 @@ public class IwlanDataServiceTest { null, /* trafficDescriptor */ true, /* matchAllRuleAllowed */ mMockDataServiceCallback); + mTestLooper.dispatchAll(); /* Check bringUpTunnel() is called. */ verify(mMockEpdgTunnelManager, times(1)) - .bringUpTunnel(any(TunnelSetupRequest.class), any(IwlanTunnelCallback.class)); + .bringUpTunnel( + any(TunnelSetupRequest.class), + any(IwlanTunnelCallback.class), + any(IwlanTunnelMetricsImpl.class)); /* Check callback result is RESULT_SUCCESS when onOpened() is called. */ mSpyIwlanDataServiceProvider .getIwlanTunnelCallback() .onOpened(TEST_APN_NAME, mMockTunnelLinkProperties); + mTestLooper.dispatchAll(); verify(mMockDataServiceCallback, times(1)) .onSetupDataCallComplete( eq(DataServiceCallback.RESULT_SUCCESS), any(DataCallResponse.class)); @@ -730,17 +1418,18 @@ public class IwlanDataServiceTest { @Test public void testIwlanTunnelStatsFailureCounts() { - DataProfile dp = buildDataProfile(); + DataProfile dp = buildImsDataProfile(); + + onSystemDefaultNetworkConnected( + mMockNetwork, mLinkProperties, TRANSPORT_WIFI, INVALID_SUB_INDEX); - mIwlanDataService.setNetworkConnected(true, mMockNetwork, IwlanDataService.Transport.WIFI); - doReturn(mMockEpdgTunnelManager).when(mSpyIwlanDataServiceProvider).getTunnelManager(); when(ErrorPolicyManager.getInstance(eq(mMockContext), eq(DEFAULT_SLOT_INDEX))) .thenReturn(mMockErrorPolicyManager); long count = 3L; for (int i = 0; i < count; i++) { mockTunnelSetupFail(dp); - sleep(1000); + mTestLooper.dispatchAll(); } IwlanDataServiceProvider.IwlanDataTunnelStats stats = @@ -751,14 +1440,19 @@ public class IwlanDataServiceTest { @Test public void testIwlanTunnelStatsUnsolDownCounts() { - DataProfile dp = buildDataProfile(); + DataProfile dp = buildImsDataProfile(); - mIwlanDataService.setNetworkConnected(true, mMockNetwork, IwlanDataService.Transport.WIFI); - doReturn(mMockEpdgTunnelManager).when(mSpyIwlanDataServiceProvider).getTunnelManager(); + when(ErrorPolicyManager.getInstance(eq(mMockContext), eq(DEFAULT_SLOT_INDEX))) + .thenReturn(mMockErrorPolicyManager); + when(mMockErrorPolicyManager.getDataFailCause(eq(TEST_APN_NAME))) + .thenReturn(DataFailCause.ERROR_UNSPECIFIED); + + onSystemDefaultNetworkConnected( + mMockNetwork, mLinkProperties, TRANSPORT_WIFI, INVALID_SUB_INDEX); long count = 3L; for (int i = 0; i < count; i++) { - mockTunnelSetupSuccess(dp, 0); + mockTunnelSetupSuccess(dp, 0, null); mockUnsolTunnelDown(); } @@ -770,35 +1464,38 @@ public class IwlanDataServiceTest { @Test public void testIwlanTunnelStats() { - DataProfile dp = buildDataProfile(); + DataProfile dp = buildImsDataProfile(); + Calendar calendar = mock(Calendar.class); + when(calendar.getTime()).thenAnswer(i -> new Date(mMockedCalendarTime)); - mIwlanDataService.setNetworkConnected(true, mMockNetwork, IwlanDataService.Transport.WIFI); - doReturn(mMockEpdgTunnelManager).when(mSpyIwlanDataServiceProvider).getTunnelManager(); + mSpyIwlanDataServiceProvider.setCalendar(calendar); + onSystemDefaultNetworkConnected( + mMockNetwork, mLinkProperties, TRANSPORT_WIFI, INVALID_SUB_INDEX); LongSummaryStatistics tunnelSetupSuccessStats = new LongSummaryStatistics(); LongSummaryStatistics tunnelUpStats = new LongSummaryStatistics(); - Date beforeSetup = Calendar.getInstance().getTime(); - mockTunnelSetupSuccess(dp, 0); - Date tunnelUp = Calendar.getInstance().getTime(); - mockDeactivateTunnelDown(0); - Date tunnelDown = Calendar.getInstance().getTime(); + Date beforeSetup = calendar.getTime(); + mockTunnelSetupSuccess(dp, 0, calendar); + Date tunnelUp = calendar.getTime(); + mockDeactivateTunnel(0, calendar); + Date tunnelDown = calendar.getTime(); tunnelSetupSuccessStats.accept(tunnelUp.getTime() - beforeSetup.getTime()); tunnelUpStats.accept(tunnelDown.getTime() - tunnelUp.getTime()); - beforeSetup = Calendar.getInstance().getTime(); - mockTunnelSetupSuccess(dp, 1000); - tunnelUp = Calendar.getInstance().getTime(); - mockDeactivateTunnelDown(3000); - tunnelDown = Calendar.getInstance().getTime(); + beforeSetup = calendar.getTime(); + mockTunnelSetupSuccess(dp, 1000, calendar); + tunnelUp = calendar.getTime(); + mockDeactivateTunnel(3000, calendar); + tunnelDown = calendar.getTime(); tunnelSetupSuccessStats.accept(tunnelUp.getTime() - beforeSetup.getTime()); tunnelUpStats.accept(tunnelDown.getTime() - tunnelUp.getTime()); - beforeSetup = Calendar.getInstance().getTime(); - mockTunnelSetupSuccess(dp, 600); - tunnelUp = Calendar.getInstance().getTime(); - mockDeactivateTunnelDown(500); - tunnelDown = Calendar.getInstance().getTime(); + beforeSetup = calendar.getTime(); + mockTunnelSetupSuccess(dp, 600, calendar); + tunnelUp = calendar.getTime(); + mockDeactivateTunnel(500, calendar); + tunnelDown = calendar.getTime(); tunnelSetupSuccessStats.accept(tunnelUp.getTime() - beforeSetup.getTime()); tunnelUpStats.accept(tunnelDown.getTime() - tunnelUp.getTime()); @@ -807,13 +1504,182 @@ public class IwlanDataServiceTest { LongSummaryStatistics finalSetupStats = stats.mTunnelSetupSuccessStats.get(TEST_APN_NAME); LongSummaryStatistics finalUpStats = stats.mTunnelUpStats.get(TEST_APN_NAME); - assertEquals(finalSetupStats.getAverage(), tunnelSetupSuccessStats.getAverage(), 100); - assertEquals(finalSetupStats.getCount(), tunnelSetupSuccessStats.getCount()); - assertEquals(finalSetupStats.getMax(), tunnelSetupSuccessStats.getMax(), 100); + assertEquals(tunnelSetupSuccessStats.getAverage(), finalSetupStats.getAverage(), 0); + assertEquals(tunnelSetupSuccessStats.getCount(), finalSetupStats.getCount()); + assertEquals(tunnelSetupSuccessStats.getMax(), finalSetupStats.getMax(), 0); + + assertEquals(tunnelUpStats.getAverage(), finalUpStats.getAverage(), 0); + assertEquals(tunnelUpStats.getCount(), finalUpStats.getCount()); + assertEquals(tunnelUpStats.getMax(), finalUpStats.getMax(), 0); + } - assertEquals(finalUpStats.getAverage(), tunnelUpStats.getAverage(), 100); - assertEquals(finalUpStats.getCount(), tunnelUpStats.getCount()); - assertEquals(finalUpStats.getMax(), tunnelUpStats.getMax(), 100); + @Test + public void testUnexpectedTunnelClosedIsSuppressed() { + mockUnsolTunnelDown(); + } + + @Test + public void testIwlanDataServiceHandlerOnUnbind() { + DataProfile dp = buildImsDataProfile(); + + mSpyIwlanDataServiceProvider.setTunnelState( + dp, + mMockDataServiceCallback, + TunnelState.TUNNEL_UP, + null /* linkProperties */, + false /* isHandover */, + 1 /* pduSessionId */, + true /* isImsOrEmergency */); + + mSpyIwlanDataServiceProvider.setMetricsAtom( + TEST_APN_NAME, + 64, // type IMS + true, + 13, // LTE + false, + true, + 1 // Transport Wi-Fi + ); + + when(ErrorPolicyManager.getInstance(eq(mMockContext), eq(DEFAULT_SLOT_INDEX))) + .thenReturn(mMockErrorPolicyManager); + when(mMockErrorPolicyManager.getDataFailCause(eq(TEST_APN_NAME))) + .thenReturn(DataFailCause.ERROR_UNSPECIFIED); + + // Simulate IwlanDataService.onUnbind() which force close all tunnels + mSpyIwlanDataServiceProvider.forceCloseTunnels(); + // Simulate DataService.onUnbind() which remove all IwlanDataServiceProviders + mSpyIwlanDataServiceProvider.close(); + mTestLooper.dispatchAll(); + + verify(mMockEpdgTunnelManager, atLeastOnce()) + .closeTunnel( + eq(TEST_APN_NAME), + eq(true), + any(IwlanTunnelCallback.class), + any(IwlanTunnelMetricsImpl.class)); + assertNotNull(mIwlanDataService.mIwlanDataServiceHandler); + // Should not raise NullPointerException + mSpyIwlanDataServiceProvider + .getIwlanTunnelCallback() + .onClosed(TEST_APN_NAME, new IwlanError(IwlanError.NO_ERROR)); + mTestLooper.dispatchAll(); + } + + @Test + public void testBackToBackOnBindAndOnUnbindDoesNotThrow() { + mIwlanDataService.onBind(null); + mIwlanDataService.onUnbind(null); + } + + @Test + public void testMetricsWhenTunnelClosedWithWrappedException() { + DataProfile dp = buildImsDataProfile(); + + mSpyIwlanDataServiceProvider.setTunnelState( + dp, + mMockDataServiceCallback, + TunnelState.TUNNEL_IN_BRINGUP, + null, /* linkProperties */ + false /* isHandover */, + 1 /* pduSessionId */, + true /* isImsOrEmergency */); + + mSpyIwlanDataServiceProvider.setMetricsAtom( + TEST_APN_NAME, + 64, // type IMS + true, + 13, // LTE + false, + true, + 1 // Transport Wi-Fi + ); + + MetricsAtom metricsAtom = mSpyIwlanDataServiceProvider.getMetricsAtomByApn(TEST_APN_NAME); + assertNotNull(metricsAtom); + + String exceptionMessage = "Some exception message"; + Exception mockException = spy(new IllegalStateException(exceptionMessage)); + String firstDeclaringClassName = "test.test.TestClass"; + String firstMethodName = "someMethod"; + String firstFileName = "TestClass.java"; + int firstLineNumber = 12345; + StackTraceElement[] stackTraceElements = { + new StackTraceElement( + firstDeclaringClassName, firstMethodName, firstFileName, firstLineNumber), + new StackTraceElement("test", "test", "test.java", 123) + }; + doReturn(stackTraceElements).when(mockException).getStackTrace(); + + when(ErrorPolicyManager.getInstance(eq(mMockContext), eq(DEFAULT_SLOT_INDEX))) + .thenReturn(mMockErrorPolicyManager); + when(mMockErrorPolicyManager.getDataFailCause(eq(TEST_APN_NAME))) + .thenReturn(DataFailCause.ERROR_UNSPECIFIED); + + mSpyIwlanDataServiceProvider + .getIwlanTunnelCallback() + .onClosed(TEST_APN_NAME, new IwlanError(new IkeInternalException(mockException))); + + mTestLooper.dispatchAll(); + + var expectedStackFirstFrame = + firstDeclaringClassName + + "." + + firstMethodName + + "(" + + firstFileName + + ":" + + firstLineNumber + + ")"; + + assertEquals( + mockException.getClass().getCanonicalName(), + metricsAtom.getIwlanErrorWrappedClassname()); + + assertEquals(expectedStackFirstFrame, metricsAtom.getIwlanErrorWrappedStackFirstFrame()); + } + + @Test + public void testMetricsWhenTunnelClosedWithoutWrappedException() { + DataProfile dp = buildImsDataProfile(); + + mSpyIwlanDataServiceProvider.setTunnelState( + dp, + mMockDataServiceCallback, + TunnelState.TUNNEL_IN_BRINGUP, + null, /* linkProperties */ + false /* isHandover */, + 1 /* pduSessionId */, + true /* isImsOrEmergency */); + + mSpyIwlanDataServiceProvider.setMetricsAtom( + TEST_APN_NAME, + 64, // type IMS + true, + 13, // LTE + false, + true, + 1 // Transport Wi-Fi + ); + + MetricsAtom metricsAtom = mSpyIwlanDataServiceProvider.getMetricsAtomByApn(TEST_APN_NAME); + assertNotNull(metricsAtom); + + when(ErrorPolicyManager.getInstance(eq(mMockContext), eq(DEFAULT_SLOT_INDEX))) + .thenReturn(mMockErrorPolicyManager); + when(mMockErrorPolicyManager.getDataFailCause(eq(TEST_APN_NAME))) + .thenReturn(DataFailCause.ERROR_UNSPECIFIED); + + mSpyIwlanDataServiceProvider + .getIwlanTunnelCallback() + .onClosed( + TEST_APN_NAME, + new IwlanError(IwlanError.EPDG_SELECTOR_SERVER_SELECTION_FAILED)); + + mTestLooper.dispatchAll(); + + assertEquals(null, metricsAtom.getIwlanErrorWrappedClassname()); + assertEquals(null, metricsAtom.getIwlanErrorWrappedStackFirstFrame()); } private void mockTunnelSetupFail(DataProfile dp) { @@ -831,17 +1697,21 @@ public class IwlanDataServiceTest { mMockDataServiceCallback); doReturn(true) .when(mMockEpdgTunnelManager) - .bringUpTunnel(any(TunnelSetupRequest.class), any(IwlanTunnelCallback.class)); + .bringUpTunnel( + any(TunnelSetupRequest.class), + any(IwlanTunnelCallback.class), + any(IwlanTunnelMetricsImpl.class)); mSpyIwlanDataServiceProvider .getIwlanTunnelCallback() .onClosed(TEST_APN_NAME, new IwlanError(IwlanError.IKE_INTERNAL_IO_EXCEPTION)); + mTestLooper.dispatchAll(); verify(mMockDataServiceCallback, atLeastOnce()) .onSetupDataCallComplete( eq(DataServiceCallback.RESULT_SUCCESS), any(DataCallResponse.class)); } - private void mockTunnelSetupSuccess(DataProfile dp, long sleepTime) { + private void mockTunnelSetupSuccess(DataProfile dp, long setupTime, Calendar calendar) { mSpyIwlanDataServiceProvider.setupDataCall( AccessNetworkType.IWLAN, /* AccessNetworkType */ dp, /* dataProfile */ @@ -856,13 +1726,18 @@ public class IwlanDataServiceTest { mMockDataServiceCallback); doReturn(true) .when(mMockEpdgTunnelManager) - .bringUpTunnel(any(TunnelSetupRequest.class), any(IwlanTunnelCallback.class)); + .bringUpTunnel( + any(TunnelSetupRequest.class), + any(IwlanTunnelCallback.class), + any(IwlanTunnelMetricsImpl.class)); + mTestLooper.dispatchAll(); - sleep(sleepTime); + advanceCalendarByTimeMs(setupTime, calendar); mSpyIwlanDataServiceProvider .getIwlanTunnelCallback() .onOpened(TEST_APN_NAME, mMockTunnelLinkProperties); + mTestLooper.dispatchAll(); verify(mMockDataServiceCallback, atLeastOnce()) .onSetupDataCallComplete( eq(DataServiceCallback.RESULT_SUCCESS), any(DataCallResponse.class)); @@ -872,21 +1747,37 @@ public class IwlanDataServiceTest { mSpyIwlanDataServiceProvider .getIwlanTunnelCallback() .onClosed(TEST_APN_NAME, new IwlanError(IwlanError.IKE_INTERNAL_IO_EXCEPTION)); + mTestLooper.dispatchAll(); } - private void mockDeactivateTunnelDown(long sleepTime) { + private void mockDeactivateTunnel(long deactivationTime, Calendar calendar) { mSpyIwlanDataServiceProvider.deactivateDataCall( TEST_APN_NAME.hashCode() /* cid: hashcode() of "ims" */, DataService.REQUEST_REASON_NORMAL /* DataService.REQUEST_REASON_NORMAL */, mMockDataServiceCallback); - verify(mMockEpdgTunnelManager, atLeastOnce()).closeTunnel(eq(TEST_APN_NAME), anyBoolean()); + mTestLooper.dispatchAll(); + verify(mMockEpdgTunnelManager, atLeastOnce()) + .closeTunnel( + eq(TEST_APN_NAME), + anyBoolean(), + any(IwlanTunnelCallback.class), + any(IwlanTunnelMetricsImpl.class)); - sleep(sleepTime); + advanceCalendarByTimeMs(deactivationTime, calendar); mSpyIwlanDataServiceProvider .getIwlanTunnelCallback() .onClosed(TEST_APN_NAME, new IwlanError(IwlanError.NO_ERROR)); + mTestLooper.dispatchAll(); verify(mMockDataServiceCallback, atLeastOnce()) .onDeactivateDataCallComplete(eq(DataServiceCallback.RESULT_SUCCESS)); } + + private Object callUnsupportedAppUsageMethod( + Object target, String methodName, Class<?>[] parameterTypes, Object[] args) + throws Exception { + Method method = target.getClass().getDeclaredMethod(methodName, parameterTypes); + method.setAccessible(true); + return method.invoke(target, args); + } } diff --git a/test/com/google/android/iwlan/IwlanEventListenerTest.java b/test/com/google/android/iwlan/IwlanEventListenerTest.java index 789ab25..79d53c0 100644 --- a/test/com/google/android/iwlan/IwlanEventListenerTest.java +++ b/test/com/google/android/iwlan/IwlanEventListenerTest.java @@ -18,7 +18,6 @@ package com.google.android.iwlan; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; -import static org.junit.Assert.*; import static org.mockito.Mockito.*; import android.content.ContentResolver; @@ -93,15 +92,14 @@ public class IwlanEventListenerTest { when(mMockContext.getSystemService(eq(SubscriptionManager.class))) .thenReturn(mMockSubscriptionManager); - when(mMockSubscriptionManager.getActiveSubscriptionInfoForSimSlotIndex( - eq(DEFAULT_SLOT_INDEX))) + when(mMockSubscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(anyInt())) .thenReturn(mMockSubscriptionInfo); when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver); when(mMockImsMmTelManager.isVoWiFiSettingEnabled()).thenReturn(true).thenReturn(false); - when(mMockImsManager.getImsMmTelManager(eq(2))).thenReturn(mMockImsMmTelManager); + when(mMockImsManager.getImsMmTelManager(anyInt())).thenReturn(mMockImsMmTelManager); when(mMockContext.getSystemService(eq(ImsManager.class))).thenReturn(mMockImsManager); @@ -111,6 +109,7 @@ public class IwlanEventListenerTest { when(mMockTelephonyManager.createForSubscriptionId(eq(0))) .thenReturn(mMockTelephonyManager); + IwlanEventListener.resetAllInstances(); mIwlanEventListener = IwlanEventListener.getInstance(mMockContext, DEFAULT_SLOT_INDEX); } @@ -121,7 +120,10 @@ public class IwlanEventListenerTest { @Test public void testWifiApChanged() throws Exception { - when(mMockHandler.obtainMessage(eq(IwlanEventListener.WIFI_AP_CHANGED_EVENT))) + when(mMockHandler.obtainMessage( + eq(IwlanEventListener.WIFI_AP_CHANGED_EVENT), + eq(DEFAULT_SLOT_INDEX), + anyInt())) .thenReturn(mMockMessage); events = new ArrayList<Integer>(); @@ -140,7 +142,10 @@ public class IwlanEventListenerTest { @Test public void testCrossSimCallingSettingEnableChanged() throws Exception { - when(mMockHandler.obtainMessage(eq(IwlanEventListener.CROSS_SIM_CALLING_ENABLE_EVENT))) + when(mMockHandler.obtainMessage( + eq(IwlanEventListener.CROSS_SIM_CALLING_ENABLE_EVENT), + eq(DEFAULT_SLOT_INDEX), + anyInt())) .thenReturn(mMockMessage); events = new ArrayList<Integer>(); @@ -161,7 +166,10 @@ public class IwlanEventListenerTest { @Test public void testCrossSimCallingSettingDisableChanged() throws Exception { - when(mMockHandler.obtainMessage(eq(IwlanEventListener.CROSS_SIM_CALLING_DISABLE_EVENT))) + when(mMockHandler.obtainMessage( + eq(IwlanEventListener.CROSS_SIM_CALLING_DISABLE_EVENT), + eq(DEFAULT_SLOT_INDEX), + anyInt())) .thenReturn(mMockMessage); events = new ArrayList<Integer>(); @@ -182,10 +190,15 @@ public class IwlanEventListenerTest { @Test public void testOnReceivedCarrierConfigChangedIntent() throws Exception { - when(mMockHandler.obtainMessage(eq(IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT))) + when(mMockHandler.obtainMessage( + eq(IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT), + eq(DEFAULT_SLOT_INDEX), + anyInt())) .thenReturn(mMockMessage); when(mMockHandler.obtainMessage( - eq(IwlanEventListener.CARRIER_CONFIG_UNKNOWN_CARRIER_EVENT))) + eq(IwlanEventListener.CARRIER_CONFIG_UNKNOWN_CARRIER_EVENT), + eq(DEFAULT_SLOT_INDEX), + anyInt())) .thenReturn(mMockMessage_2); events = new ArrayList<Integer>(); @@ -217,9 +230,15 @@ public class IwlanEventListenerTest { @Test public void testWfcSettingChanged() throws Exception { - when(mMockHandler.obtainMessage(eq(IwlanEventListener.WIFI_CALLING_ENABLE_EVENT))) + when(mMockHandler.obtainMessage( + eq(IwlanEventListener.WIFI_CALLING_ENABLE_EVENT), + eq(DEFAULT_SLOT_INDEX), + anyInt())) .thenReturn(mMockMessage); - when(mMockHandler.obtainMessage(eq(IwlanEventListener.WIFI_CALLING_DISABLE_EVENT))) + when(mMockHandler.obtainMessage( + eq(IwlanEventListener.WIFI_CALLING_DISABLE_EVENT), + eq(DEFAULT_SLOT_INDEX), + anyInt())) .thenReturn(mMockMessage_2); events = new ArrayList<Integer>(); @@ -242,7 +261,11 @@ public class IwlanEventListenerTest { .when(IwlanHelper.getSubId(eq(mMockContext), eq(DEFAULT_SLOT_INDEX))) .thenReturn(0); - when(mMockHandler.obtainMessage(eq(IwlanEventListener.CELLINFO_CHANGED_EVENT), eq(arrayCi))) + when(mMockHandler.obtainMessage( + eq(IwlanEventListener.CELLINFO_CHANGED_EVENT), + eq(DEFAULT_SLOT_INDEX), + anyInt(), + eq(arrayCi))) .thenReturn(mMockMessage); events = new ArrayList<Integer>(); @@ -257,4 +280,46 @@ public class IwlanEventListenerTest { verify(mMockMessage, times(1)).sendToTarget(); } + + @Test + public void testCallStateChanged() throws Exception { + when(mMockHandler.obtainMessage( + eq(IwlanEventListener.CALL_STATE_CHANGED_EVENT), + eq(DEFAULT_SLOT_INDEX), + eq(TelephonyManager.CALL_STATE_OFFHOOK))) + .thenReturn(mMockMessage); + + events = new ArrayList<Integer>(); + events.add(IwlanEventListener.CALL_STATE_CHANGED_EVENT); + mIwlanEventListener.addEventListener(events, mMockHandler); + + mIwlanEventListener.registerTelephonyCallback(); + + TelephonyCallback.CallStateListener mTelephonyCallback = + mIwlanEventListener.getTelephonyCallback(); + mTelephonyCallback.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK); + + verify(mMockMessage, times(1)).sendToTarget(); + } + + @Test + public void testWfcChangeThrowIAE() throws Exception { + when(mMockHandler.obtainMessage( + eq(IwlanEventListener.WIFI_CALLING_DISABLE_EVENT), + eq(DEFAULT_SLOT_INDEX), + anyInt())) + .thenReturn(mMockMessage); + + events = new ArrayList<Integer>(); + events.add(IwlanEventListener.WIFI_CALLING_DISABLE_EVENT); + mIwlanEventListener.addEventListener(events, mMockHandler); + mIwlanEventListener.setWfcEnabledUri(WFC_ENABLED_URI); + + doThrow(new IllegalArgumentException("IllegalArgumentException at isVoWiFiSettingEnabled")) + .when(mMockImsMmTelManager) + .isVoWiFiSettingEnabled(); + + mIwlanEventListener.notifyCurrentSetting(WFC_ENABLED_URI); + verify(mMockMessage, times(1)).sendToTarget(); + } } diff --git a/test/com/google/android/iwlan/IwlanNetworkServiceTest.java b/test/com/google/android/iwlan/IwlanNetworkServiceTest.java index 000190a..0633900 100644 --- a/test/com/google/android/iwlan/IwlanNetworkServiceTest.java +++ b/test/com/google/android/iwlan/IwlanNetworkServiceTest.java @@ -21,8 +21,13 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess import static org.junit.Assert.*; import static org.mockito.Mockito.*; +import android.annotation.Nullable; import android.content.Context; import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.TelephonyNetworkSpecifier; +import android.net.vcn.VcnTransportInfo; import android.telephony.AccessNetworkConstants; import android.telephony.INetworkService; import android.telephony.INetworkServiceCallback; @@ -31,6 +36,8 @@ import android.telephony.NetworkServiceCallback; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.telephony.ims.ImsManager; +import android.telephony.ims.ImsMmTelManager; import com.google.android.iwlan.IwlanNetworkService.IwlanNetworkServiceProvider; @@ -40,18 +47,23 @@ import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; import java.util.Arrays; public class IwlanNetworkServiceTest { private static final String TAG = IwlanNetworkServiceTest.class.getSimpleName(); private static final int DEFAULT_SLOT_INDEX = 0; + private static final int DEFAULT_SUB_INDEX = 0; @Mock private Context mMockContext; @Mock private ConnectivityManager mMockConnectivityManager; @Mock private SubscriptionManager mMockSubscriptionManager; @Mock private SubscriptionInfo mMockSubscriptionInfo; + @Mock private ImsManager mMockImsManager; + @Mock private ImsMmTelManager mMockImsMmTelManager; @Mock private INetworkServiceCallback mCallback; + @Mock private Network mMockNetwork; MockitoSession mStaticMockSession; IwlanNetworkService mIwlanNetworkService; @@ -64,8 +76,8 @@ public class IwlanNetworkServiceTest { mStaticMockSession = mockitoSession() - .mockStatic(IwlanHelper.class) .mockStatic(SubscriptionManager.class) + .strictness(Strictness.LENIENT) .startMocking(); when(mMockContext.getSystemService(eq(ConnectivityManager.class))) @@ -77,10 +89,17 @@ public class IwlanNetworkServiceTest { when(mMockSubscriptionManager.getActiveSubscriptionInfoForSimSlotIndex( eq(DEFAULT_SLOT_INDEX))) .thenReturn(mMockSubscriptionInfo); + when(mMockSubscriptionManager.getDefaultDataSubscriptionId()).thenReturn(DEFAULT_SUB_INDEX); + when(mMockSubscriptionManager.getSlotIndex(DEFAULT_SUB_INDEX)) + .thenReturn(DEFAULT_SLOT_INDEX); + when(mMockSubscriptionManager.getSlotIndex(DEFAULT_SUB_INDEX + 1)) + .thenReturn(DEFAULT_SLOT_INDEX + 1); - lenient() - .when(SubscriptionManager.from(eq(mMockContext))) - .thenReturn(mMockSubscriptionManager); + when(mMockSubscriptionInfo.getSubscriptionId()).thenReturn(DEFAULT_SUB_INDEX); + + when(mMockContext.getSystemService(eq(ImsManager.class))).thenReturn(mMockImsManager); + + when(mMockImsManager.getImsMmTelManager(anyInt())).thenReturn(mMockImsMmTelManager); mIwlanNetworkService = new IwlanNetworkService(); mIwlanNetworkService.setAppContext(mMockContext); @@ -97,22 +116,23 @@ public class IwlanNetworkServiceTest { mStaticMockSession.finishMocking(); } - @Test - public void testRequestNetworkRegistrationInfo() throws Exception { - int domain = NetworkRegistrationInfo.DOMAIN_PS; - boolean mIsSubActive = true; - long startTime; - + @Nullable + IwlanNetworkServiceProvider initNSP() { // Wait for IwlanNetworkServiceProvider created and timeout is 1 second. - startTime = System.currentTimeMillis(); + long startTime = System.currentTimeMillis(); + IwlanNetworkServiceProvider nsp = null; while (System.currentTimeMillis() - startTime < 1000) { - mIwlanNetworkServiceProvider = - mIwlanNetworkService.getNetworkServiceProvider(DEFAULT_SLOT_INDEX); - if (mIwlanNetworkServiceProvider != null) { + nsp = mIwlanNetworkService.getNetworkServiceProvider(DEFAULT_SLOT_INDEX); + if (nsp != null) { break; } } + return nsp; + } + @Test + public void testRequestNetworkRegistrationInfo() throws Exception { + mIwlanNetworkServiceProvider = initNSP(); assertTrue(mIwlanNetworkServiceProvider != null); // Set Wifi on and verify mCallback should receive onNetworkStateChanged. @@ -126,223 +146,202 @@ public class IwlanNetworkServiceTest { // Create expected NetworkRegistrationInfo NetworkRegistrationInfo.Builder expectedStateBuilder = generateStateBuilder( - domain, mIsSubActive, NetworkRegistrationInfo.REGISTRATION_STATE_HOME); + NetworkRegistrationInfo.DOMAIN_PS, + true /* isSubActive */, + NetworkRegistrationInfo.REGISTRATION_STATE_HOME); - mBinder.requestNetworkRegistrationInfo(0, domain, mCallback); + mBinder.requestNetworkRegistrationInfo(0, NetworkRegistrationInfo.DOMAIN_PS, mCallback); verify(mCallback, timeout(1000).times(1)) .onRequestNetworkRegistrationInfoComplete( eq(NetworkServiceCallback.RESULT_SUCCESS), eq(expectedStateBuilder.build())); - - IwlanNetworkService.setNetworkConnected( - false, IwlanNetworkService.Transport.UNSPECIFIED_NETWORK); } - @Test - public void testNetworkRegistrationInfoForCellularAndCstDisabled() throws Exception { - int domain = NetworkRegistrationInfo.DOMAIN_PS; - boolean mIsSubActive = true; - long startTime; - - // Wait for IwlanNetworkServiceProvider created and timeout is 1 second. - startTime = System.currentTimeMillis(); - while (System.currentTimeMillis() - startTime < 1000) { - mIwlanNetworkServiceProvider = - mIwlanNetworkService.getNetworkServiceProvider(DEFAULT_SLOT_INDEX); - if (mIwlanNetworkServiceProvider != null) { - break; - } + private NetworkCapabilities prepareCellularNetworkCapabilitiesForTest( + int subId, boolean isVcn) { + NetworkCapabilities.Builder builder = + new NetworkCapabilities.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); + if (isVcn) { + builder.setTransportInfo(new VcnTransportInfo(subId)); + } else { + builder.setNetworkSpecifier(new TelephonyNetworkSpecifier(subId)); } + return builder.build(); + } - lenient() - .when( - IwlanHelper.isCrossSimCallingEnabled( - eq(mMockContext), eq(DEFAULT_SLOT_INDEX))) - .thenReturn(false); - lenient() - .when(IwlanHelper.isDefaultDataSlot(eq(mMockContext), eq(DEFAULT_SLOT_INDEX))) - .thenReturn(true); + private NetworkCapabilities prepareWifiNetworkCapabilitiesForTest() { + return new NetworkCapabilities.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .build(); + } + @Test + public void testNetworkRegistrationInfoSearchingForCellularAndCstDisabled() throws Exception { + mIwlanNetworkServiceProvider = initNSP(); assertTrue(mIwlanNetworkServiceProvider != null); - // Set Network on and verify mCallback should receive onNetworkStateChanged. - mIwlanNetworkService.setNetworkConnected(true, IwlanNetworkService.Transport.MOBILE); - verify(mCallback, timeout(1000).times(1)).onNetworkStateChanged(); + when(mMockImsMmTelManager.isCrossSimCallingEnabled()).thenReturn(false); - // Set Sub active and verify mCallback should receive onNetworkStateChanged. + NetworkCapabilities nc = + prepareCellularNetworkCapabilitiesForTest(DEFAULT_SUB_INDEX, false /* is Vcn */); + mIwlanNetworkService.getNetworkMonitorCallback().onCapabilitiesChanged(mMockNetwork, nc); mIwlanNetworkServiceProvider.subscriptionChanged(); - verify(mCallback, timeout(1000).times(2)).onNetworkStateChanged(); // Create expected NetworkRegistrationInfo NetworkRegistrationInfo.Builder expectedStateBuilder = generateStateBuilder( - domain, - mIsSubActive, + NetworkRegistrationInfo.DOMAIN_PS, + true /* isSubActive */, NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_SEARCHING); - mBinder.requestNetworkRegistrationInfo(0, domain, mCallback); + mBinder.requestNetworkRegistrationInfo(0, NetworkRegistrationInfo.DOMAIN_PS, mCallback); verify(mCallback, timeout(1000).times(1)) .onRequestNetworkRegistrationInfoComplete( eq(NetworkServiceCallback.RESULT_SUCCESS), eq(expectedStateBuilder.build())); - - IwlanNetworkService.setNetworkConnected( - false, IwlanNetworkService.Transport.UNSPECIFIED_NETWORK); } @Test - public void testNetworkRegistrationInfoForCellularAndCstEnabled() throws Exception { - int domain = NetworkRegistrationInfo.DOMAIN_PS; - boolean mIsSubActive = true; - long startTime; - - // Wait for IwlanNetworkServiceProvider created and timeout is 1 second. - startTime = System.currentTimeMillis(); - while (System.currentTimeMillis() - startTime < 1000) { - mIwlanNetworkServiceProvider = - mIwlanNetworkService.getNetworkServiceProvider(DEFAULT_SLOT_INDEX); - if (mIwlanNetworkServiceProvider != null) { - break; - } - } - - lenient() - .when( - IwlanHelper.isCrossSimCallingEnabled( - eq(mMockContext), eq(DEFAULT_SLOT_INDEX))) - .thenReturn(true); - lenient() - .when(IwlanHelper.isDefaultDataSlot(eq(mMockContext), eq(DEFAULT_SLOT_INDEX))) - .thenReturn(false); - + public void testNetworkRegistrationInfoSearchingForCellularOnSameSubAndCstEnabled() + throws Exception { + mIwlanNetworkServiceProvider = initNSP(); assertTrue(mIwlanNetworkServiceProvider != null); - // Set Network on and verify mCallback should receive onNetworkStateChanged. - mIwlanNetworkService.setNetworkConnected(true, IwlanNetworkService.Transport.MOBILE); - verify(mCallback, timeout(1000).times(1)).onNetworkStateChanged(); + when(mMockImsMmTelManager.isCrossSimCallingEnabled()).thenReturn(true); - // Set Sub active and verify mCallback should receive onNetworkStateChanged. + NetworkCapabilities nc = + prepareCellularNetworkCapabilitiesForTest(DEFAULT_SUB_INDEX, false /* is Vcn */); + mIwlanNetworkService.getNetworkMonitorCallback().onCapabilitiesChanged(mMockNetwork, nc); mIwlanNetworkServiceProvider.subscriptionChanged(); - verify(mCallback, timeout(1000).times(2)).onNetworkStateChanged(); // Create expected NetworkRegistrationInfo NetworkRegistrationInfo.Builder expectedStateBuilder = generateStateBuilder( - domain, mIsSubActive, NetworkRegistrationInfo.REGISTRATION_STATE_HOME); + NetworkRegistrationInfo.DOMAIN_PS, + true /* mIsSubActive */, + NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_SEARCHING); - mBinder.requestNetworkRegistrationInfo(0, domain, mCallback); + mBinder.requestNetworkRegistrationInfo(0, NetworkRegistrationInfo.DOMAIN_PS, mCallback); verify(mCallback, timeout(1000).times(1)) .onRequestNetworkRegistrationInfoComplete( eq(NetworkServiceCallback.RESULT_SUCCESS), eq(expectedStateBuilder.build())); - - IwlanNetworkService.setNetworkConnected( - false, IwlanNetworkService.Transport.UNSPECIFIED_NETWORK); } @Test - public void testNetworkRegistrationInfoForWiFiAndCstEnabled() throws Exception { - int domain = NetworkRegistrationInfo.DOMAIN_PS; - boolean mIsSubActive = true; - long startTime; + public void testNetworkRegistrationInfoHomeForCellularOnDifferentSubAndCstEnabled() + throws Exception { + mIwlanNetworkServiceProvider = initNSP(); + assertTrue(mIwlanNetworkServiceProvider != null); - // Wait for IwlanNetworkServiceProvider created and timeout is 1 second. - startTime = System.currentTimeMillis(); - while (System.currentTimeMillis() - startTime < 1000) { - mIwlanNetworkServiceProvider = - mIwlanNetworkService.getNetworkServiceProvider(DEFAULT_SLOT_INDEX); - if (mIwlanNetworkServiceProvider != null) { - break; - } - } + when(mMockImsMmTelManager.isCrossSimCallingEnabled()).thenReturn(true); - lenient() - .when( - IwlanHelper.isCrossSimCallingEnabled( - eq(mMockContext), eq(DEFAULT_SLOT_INDEX))) - .thenReturn(true); - lenient() - .when(IwlanHelper.isDefaultDataSlot(eq(mMockContext), eq(DEFAULT_SLOT_INDEX))) - .thenReturn(true); + // Cellular data is on the other sub + NetworkCapabilities nc = + prepareCellularNetworkCapabilitiesForTest( + DEFAULT_SUB_INDEX + 1, false /* is Vcn */); + mIwlanNetworkService.getNetworkMonitorCallback().onCapabilitiesChanged(mMockNetwork, nc); + mIwlanNetworkServiceProvider.subscriptionChanged(); + // Create expected NetworkRegistrationInfo + NetworkRegistrationInfo.Builder expectedStateBuilder = + generateStateBuilder( + NetworkRegistrationInfo.DOMAIN_PS, + true /* isSubActive */, + NetworkRegistrationInfo.REGISTRATION_STATE_HOME); + + mBinder.requestNetworkRegistrationInfo(0, NetworkRegistrationInfo.DOMAIN_PS, mCallback); + + verify(mCallback, timeout(1000).times(1)) + .onRequestNetworkRegistrationInfoComplete( + eq(NetworkServiceCallback.RESULT_SUCCESS), + eq(expectedStateBuilder.build())); + } + + @Test + public void testNetworkRegistrationInfoHomeForCellularVcnOnDifferentSubAndCstEnabled() + throws Exception { + mIwlanNetworkServiceProvider = initNSP(); assertTrue(mIwlanNetworkServiceProvider != null); - // Set Network on and verify mCallback should receive onNetworkStateChanged. - mIwlanNetworkService.setNetworkConnected(true, IwlanNetworkService.Transport.WIFI); - verify(mCallback, timeout(1000).times(1)).onNetworkStateChanged(); + when(mMockImsMmTelManager.isCrossSimCallingEnabled()).thenReturn(true); - // Set Sub active and verify mCallback should receive onNetworkStateChanged. + // Cellular data as a VCN network is on the other sub + NetworkCapabilities nc = + prepareCellularNetworkCapabilitiesForTest(DEFAULT_SUB_INDEX + 1, true /* is Vcn */); + mIwlanNetworkService.getNetworkMonitorCallback().onCapabilitiesChanged(mMockNetwork, nc); mIwlanNetworkServiceProvider.subscriptionChanged(); - verify(mCallback, timeout(1000).times(2)).onNetworkStateChanged(); // Create expected NetworkRegistrationInfo NetworkRegistrationInfo.Builder expectedStateBuilder = generateStateBuilder( - domain, mIsSubActive, NetworkRegistrationInfo.REGISTRATION_STATE_HOME); + NetworkRegistrationInfo.DOMAIN_PS, + true /* isSubActive */, + NetworkRegistrationInfo.REGISTRATION_STATE_HOME); - mBinder.requestNetworkRegistrationInfo(0, domain, mCallback); + mBinder.requestNetworkRegistrationInfo(0, NetworkRegistrationInfo.DOMAIN_PS, mCallback); verify(mCallback, timeout(1000).times(1)) .onRequestNetworkRegistrationInfoComplete( eq(NetworkServiceCallback.RESULT_SUCCESS), eq(expectedStateBuilder.build())); - - IwlanNetworkService.setNetworkConnected( - false, IwlanNetworkService.Transport.UNSPECIFIED_NETWORK); } @Test - public void testNetworkRegistrationInfoForWiFiAndCstDisabled() throws Exception { - int domain = NetworkRegistrationInfo.DOMAIN_PS; - boolean mIsSubActive = true; - long startTime; + public void testNetworkRegistrationInfoHomeForWiFiAndCstEnabled() throws Exception { + mIwlanNetworkServiceProvider = initNSP(); + assertTrue(mIwlanNetworkServiceProvider != null); - // Wait for IwlanNetworkServiceProvider created and timeout is 1 second. - startTime = System.currentTimeMillis(); - while (System.currentTimeMillis() - startTime < 1000) { - mIwlanNetworkServiceProvider = - mIwlanNetworkService.getNetworkServiceProvider(DEFAULT_SLOT_INDEX); - if (mIwlanNetworkServiceProvider != null) { - break; - } - } + when(mMockImsMmTelManager.isCrossSimCallingEnabled()).thenReturn(true); - lenient() - .when( - IwlanHelper.isCrossSimCallingEnabled( - eq(mMockContext), eq(DEFAULT_SLOT_INDEX))) - .thenReturn(false); - lenient() - .when(IwlanHelper.isDefaultDataSlot(eq(mMockContext), eq(DEFAULT_SLOT_INDEX))) - .thenReturn(true); + NetworkCapabilities nc = prepareWifiNetworkCapabilitiesForTest(); + mIwlanNetworkService.getNetworkMonitorCallback().onCapabilitiesChanged(mMockNetwork, nc); + mIwlanNetworkServiceProvider.subscriptionChanged(); + // Create expected NetworkRegistrationInfo + NetworkRegistrationInfo.Builder expectedStateBuilder = + generateStateBuilder( + NetworkRegistrationInfo.DOMAIN_PS, + true /* isSubActive */, + NetworkRegistrationInfo.REGISTRATION_STATE_HOME); + + mBinder.requestNetworkRegistrationInfo(0, NetworkRegistrationInfo.DOMAIN_PS, mCallback); + + verify(mCallback, timeout(1000).times(1)) + .onRequestNetworkRegistrationInfoComplete( + eq(NetworkServiceCallback.RESULT_SUCCESS), + eq(expectedStateBuilder.build())); + } + + @Test + public void testNetworkRegistrationInfoHomeForWiFiAndCstDisabled() throws Exception { + mIwlanNetworkServiceProvider = initNSP(); assertTrue(mIwlanNetworkServiceProvider != null); - // Set Network on and verify mCallback should receive onNetworkStateChanged. - mIwlanNetworkService.setNetworkConnected(true, IwlanNetworkService.Transport.WIFI); - verify(mCallback, timeout(1000).times(1)).onNetworkStateChanged(); + when(mMockImsMmTelManager.isCrossSimCallingEnabled()).thenReturn(false); + + NetworkCapabilities nc = prepareWifiNetworkCapabilitiesForTest(); + mIwlanNetworkService.getNetworkMonitorCallback().onCapabilitiesChanged(mMockNetwork, nc); - // Set Sub active and verify mCallback should receive onNetworkStateChanged. mIwlanNetworkServiceProvider.subscriptionChanged(); - verify(mCallback, timeout(1000).times(2)).onNetworkStateChanged(); // Create expected NetworkRegistrationInfo NetworkRegistrationInfo.Builder expectedStateBuilder = generateStateBuilder( - domain, mIsSubActive, NetworkRegistrationInfo.REGISTRATION_STATE_HOME); + NetworkRegistrationInfo.DOMAIN_PS, + true /* isSubActive */, + NetworkRegistrationInfo.REGISTRATION_STATE_HOME); - mBinder.requestNetworkRegistrationInfo(0, domain, mCallback); + mBinder.requestNetworkRegistrationInfo(0, NetworkRegistrationInfo.DOMAIN_PS, mCallback); verify(mCallback, timeout(1000).times(1)) .onRequestNetworkRegistrationInfoComplete( eq(NetworkServiceCallback.RESULT_SUCCESS), eq(expectedStateBuilder.build())); - - IwlanNetworkService.setNetworkConnected( - false, IwlanNetworkService.Transport.UNSPECIFIED_NETWORK); } private NetworkRegistrationInfo.Builder generateStateBuilder( diff --git a/test/com/google/android/iwlan/TunnelMetricsInterfaceTest.java b/test/com/google/android/iwlan/TunnelMetricsInterfaceTest.java new file mode 100644 index 0000000..509037f --- /dev/null +++ b/test/com/google/android/iwlan/TunnelMetricsInterfaceTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2022 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.google.android.iwlan; + +import static org.junit.Assert.assertEquals; + +import android.net.InetAddresses; + +import com.google.android.iwlan.TunnelMetricsInterface.*; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@RunWith(JUnit4.class) +public class TunnelMetricsInterfaceTest { + private static final String TEST_EPDG_ADDRESS = "127.0.0.1"; + private static final String TEST_APN_NAME = "www.xyz.com"; + + @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + + @Test + public void testTunnelMetricsBuilder() { + TunnelMetricsData metricsData = + new TunnelMetricsData.Builder() + .setApnName(TEST_APN_NAME) + .setEpdgServerAddress(InetAddresses.parseNumericAddress(TEST_EPDG_ADDRESS)) + .build(); + assertEquals(TEST_APN_NAME, metricsData.getApnName()); + assertEquals(TEST_EPDG_ADDRESS, metricsData.getEpdgServerAddress()); + } + + @Test + public void testOnOpenedMetricsBuilder() { + OnOpenedMetrics metricsData = + new OnOpenedMetrics.Builder() + .setApnName(TEST_APN_NAME) + .setEpdgServerAddress(InetAddresses.parseNumericAddress(TEST_EPDG_ADDRESS)) + .build(); + assertEquals(TEST_APN_NAME, metricsData.getApnName()); + assertEquals(TEST_EPDG_ADDRESS, metricsData.getEpdgServerAddress()); + } + + @Test + public void testOnClosedMetricsBuilder() { + OnClosedMetrics metricsData = + new OnClosedMetrics.Builder() + .setApnName(TEST_APN_NAME) + .setEpdgServerAddress(InetAddresses.parseNumericAddress(TEST_EPDG_ADDRESS)) + .build(); + assertEquals(TEST_APN_NAME, metricsData.getApnName()); + assertEquals(TEST_EPDG_ADDRESS, metricsData.getEpdgServerAddress()); + } +} diff --git a/test/com/google/android/iwlan/epdg/EpdgSelectorTest.java b/test/com/google/android/iwlan/epdg/EpdgSelectorTest.java index 9a4af93..3f23dab 100644 --- a/test/com/google/android/iwlan/epdg/EpdgSelectorTest.java +++ b/test/com/google/android/iwlan/epdg/EpdgSelectorTest.java @@ -44,11 +44,13 @@ import android.telephony.CellInfoGsm; import android.telephony.CellInfoLte; import android.telephony.CellInfoNr; import android.telephony.CellInfoWcdma; +import android.telephony.DataFailCause; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.Log; +import com.google.android.iwlan.ErrorPolicyManager; import com.google.android.iwlan.IwlanError; import org.junit.After; @@ -75,6 +77,11 @@ public class EpdgSelectorTest { private static final String TEST_IP_ADDRESS = "127.0.0.1"; private static final String TEST_IP_ADDRESS_1 = "127.0.0.2"; private static final String TEST_IP_ADDRESS_2 = "127.0.0.3"; + private static final String TEST_IP_ADDRESS_3 = "127.0.0.4"; + private static final String TEST_IP_ADDRESS_4 = "127.0.0.5"; + private static final String TEST_IP_ADDRESS_5 = "127.0.0.6"; + private static final String TEST_IP_ADDRESS_6 = "127.0.0.7"; + private static final String TEST_IP_ADDRESS_7 = "127.0.0.8"; private static final String TEST_IPV6_ADDRESS = "0000:0000:0000:0000:0000:0000:0000:0001"; private static int testPcoIdIPv6 = 0xFF01; @@ -86,6 +93,7 @@ public class EpdgSelectorTest { @Mock private Context mMockContext; @Mock private Network mMockNetwork; + @Mock private ErrorPolicyManager mMockErrorPolicyManager; @Mock private SubscriptionManager mMockSubscriptionManager; @Mock private SubscriptionInfo mMockSubscriptionInfo; @Mock private CarrierConfigManager mMockCarrierConfigManager; @@ -108,9 +116,15 @@ public class EpdgSelectorTest { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mStaticMockSession = mockitoSession().mockStatic(DnsResolver.class).startMocking(); + mStaticMockSession = + mockitoSession() + .mockStatic(DnsResolver.class) + .mockStatic(ErrorPolicyManager.class) + .startMocking(); - mEpdgSelector = new EpdgSelector(mMockContext, DEFAULT_SLOT_INDEX); + when(ErrorPolicyManager.getInstance(mMockContext, DEFAULT_SLOT_INDEX)) + .thenReturn(mMockErrorPolicyManager); + mEpdgSelector = spy(new EpdgSelector(mMockContext, DEFAULT_SLOT_INDEX)); when(mMockContext.getSystemService(eq(SubscriptionManager.class))) .thenReturn(mMockSubscriptionManager); @@ -122,6 +136,8 @@ public class EpdgSelectorTest { when(mMockSubscriptionInfo.getMncString()).thenReturn("120"); + when(mMockTelephonyManager.getNetworkOperator()).thenReturn("311120"); + when(mMockContext.getSystemService(eq(TelephonyManager.class))) .thenReturn(mMockTelephonyManager); @@ -140,12 +156,13 @@ public class EpdgSelectorTest { // Mock carrier configs with test bundle mTestBundle = new PersistableBundle(); + mTestBundle.putInt( + CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_IP_TYPE_PREFERENCE_INT, + CarrierConfigManager.Iwlan.EPDG_ADDRESS_IPV4_PREFERRED); when(mMockContext.getSystemService(eq(CarrierConfigManager.class))) .thenReturn(mMockCarrierConfigManager); when(mMockCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(mTestBundle); - lenient().when(DnsResolver.getInstance()).thenReturn(mMockDnsResolver); - mFakeDns = new FakeDns(); mFakeDns.startMocking(); } @@ -158,6 +175,10 @@ public class EpdgSelectorTest { @Test public void testStaticMethodPass() throws Exception { + when(DnsResolver.getInstance()).thenReturn(mMockDnsResolver); + doReturn(true).when(mEpdgSelector).hasIpv4Address(mMockNetwork); + doReturn(true).when(mEpdgSelector).hasIpv6Address(mMockNetwork); + // Set DnsResolver query mock final String testStaticAddress = "epdg.epc.mnc088.mcc888.pub.3gppnetwork.org"; mFakeDns.setAnswer(testStaticAddress, new String[] {TEST_IP_ADDRESS}, TYPE_A); @@ -174,12 +195,32 @@ public class EpdgSelectorTest { InetAddress expectedAddress = InetAddress.getByName(TEST_IP_ADDRESS); - assertEquals(testInetAddresses.size(), 1); - assertEquals(testInetAddresses.get(0), expectedAddress); + assertEquals(1, testInetAddresses.size()); + assertEquals(expectedAddress, testInetAddresses.get(0)); + } + + @Test + public void testStaticMethodDirectIpAddress_noDnsResolution() throws Exception { + mTestBundle.putIntArray( + CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY, + new int[] {CarrierConfigManager.Iwlan.EPDG_ADDRESS_STATIC}); + // Carrier config directly contains the ePDG IP address. + mTestBundle.putString( + CarrierConfigManager.Iwlan.KEY_EPDG_STATIC_ADDRESS_STRING, TEST_IP_ADDRESS); + + ArrayList<InetAddress> testInetAddresses = + getValidatedServerListWithDefaultParams(false /*isEmergency*/); + + assertEquals(1, testInetAddresses.size()); + assertEquals(InetAddresses.parseNumericAddress(TEST_IP_ADDRESS), testInetAddresses.get(0)); } @Test public void testRoamStaticMethodPass() throws Exception { + when(DnsResolver.getInstance()).thenReturn(mMockDnsResolver); + doReturn(true).when(mEpdgSelector).hasIpv4Address(mMockNetwork); + doReturn(true).when(mEpdgSelector).hasIpv6Address(mMockNetwork); + // Set DnsResolver query mock final String testRoamStaticAddress = "epdg.epc.mnc088.mcc888.pub.3gppnetwork.org"; mFakeDns.setAnswer(testRoamStaticAddress, new String[] {TEST_IP_ADDRESS}, TYPE_A); @@ -197,8 +238,8 @@ public class EpdgSelectorTest { InetAddress expectedAddress = InetAddress.getByName(TEST_IP_ADDRESS); - assertEquals(testInetAddresses.size(), 1); - assertEquals(testInetAddresses.get(0), expectedAddress); + assertEquals(1, testInetAddresses.size()); + assertEquals(expectedAddress, testInetAddresses.get(0)); } @Test @@ -213,58 +254,275 @@ public class EpdgSelectorTest { @Test public void testPlmnResolutionMethodWithNoPlmnInCarrierConfig() throws Exception { + when(DnsResolver.getInstance()).thenReturn(mMockDnsResolver); + doReturn(true).when(mEpdgSelector).hasIpv4Address(mMockNetwork); + doReturn(true).when(mEpdgSelector).hasIpv6Address(mMockNetwork); + // setUp() fills default values for mcc-mnc - String expectedFqdn1 = "epdg.epc.mnc120.mcc311.pub.3gppnetwork.org"; - String expectedFqdn2 = "epdg.epc.mnc120.mcc300.pub.3gppnetwork.org"; + String expectedFqdnFromImsi = "epdg.epc.mnc120.mcc311.pub.3gppnetwork.org"; + String expectedFqdnFromEhplmn = "epdg.epc.mnc120.mcc300.pub.3gppnetwork.org"; - mFakeDns.setAnswer(expectedFqdn1, new String[] {TEST_IP_ADDRESS_1}, TYPE_A); - mFakeDns.setAnswer(expectedFqdn2, new String[] {TEST_IP_ADDRESS_2}, TYPE_A); + mFakeDns.setAnswer(expectedFqdnFromImsi, new String[] {TEST_IP_ADDRESS_1}, TYPE_A); + mFakeDns.setAnswer(expectedFqdnFromEhplmn, new String[] {TEST_IP_ADDRESS_2}, TYPE_A); ArrayList<InetAddress> testInetAddresses = getValidatedServerListWithDefaultParams(false /*isEmergency*/); - assertEquals(testInetAddresses.size(), 2); + assertEquals(2, testInetAddresses.size()); assertTrue(testInetAddresses.contains(InetAddress.getByName(TEST_IP_ADDRESS_1))); assertTrue(testInetAddresses.contains(InetAddress.getByName(TEST_IP_ADDRESS_2))); } private void testPlmnResolutionMethod(boolean isEmergency) throws Exception { - String expectedFqdn1 = - (isEmergency) - ? "sos.epdg.epc.mnc480.mcc310.pub.3gppnetwork.org" - : "epdg.epc.mnc480.mcc310.pub.3gppnetwork.org"; - String expectedFqdn2 = - (isEmergency) - ? "sos.epdg.epc.mnc120.mcc300.pub.3gppnetwork.org" - : "epdg.epc.mnc120.mcc300.pub.3gppnetwork.org"; - String expectedFqdn3 = - (isEmergency) - ? "sos.epdg.epc.mnc120.mcc311.pub.3gppnetwork.org" - : "epdg.epc.mnc120.mcc311.pub.3gppnetwork.org"; + when(DnsResolver.getInstance()).thenReturn(mMockDnsResolver); + doReturn(true).when(mEpdgSelector).hasIpv4Address(mMockNetwork); + doReturn(true).when(mEpdgSelector).hasIpv6Address(mMockNetwork); + + String expectedFqdnFromImsi = "epdg.epc.mnc120.mcc311.pub.3gppnetwork.org"; + String expectedFqdnFromRplmn = "epdg.epc.mnc121.mcc311.pub.3gppnetwork.org"; + String expectedFqdnFromEhplmn = "epdg.epc.mnc120.mcc300.pub.3gppnetwork.org"; + String excludedFqdnFromConfig = "epdg.epc.mnc480.mcc310.pub.3gppnetwork.org"; + + when(mMockTelephonyManager.getNetworkOperator()).thenReturn("311121"); mTestBundle.putIntArray( CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY, new int[] {CarrierConfigManager.Iwlan.EPDG_ADDRESS_PLMN}); mTestBundle.putStringArray( CarrierConfigManager.Iwlan.KEY_MCC_MNCS_STRING_ARRAY, - new String[] {"310-480", "300-120", "311-120"}); - - mFakeDns.setAnswer(expectedFqdn1, new String[] {TEST_IP_ADDRESS_1}, TYPE_A); - mFakeDns.setAnswer(expectedFqdn2, new String[] {TEST_IP_ADDRESS_2}, TYPE_A); - mFakeDns.setAnswer(expectedFqdn3, new String[] {TEST_IP_ADDRESS}, TYPE_A); + new String[] {"310-480", "300-120", "311-120", "311-121"}); + + mFakeDns.setAnswer(expectedFqdnFromImsi, new String[] {TEST_IP_ADDRESS}, TYPE_A); + mFakeDns.setAnswer(expectedFqdnFromEhplmn, new String[] {TEST_IP_ADDRESS_1}, TYPE_A); + mFakeDns.setAnswer(excludedFqdnFromConfig, new String[] {TEST_IP_ADDRESS_2}, TYPE_A); + mFakeDns.setAnswer("sos." + expectedFqdnFromImsi, new String[] {TEST_IP_ADDRESS_3}, TYPE_A); + mFakeDns.setAnswer( + "sos." + expectedFqdnFromEhplmn, new String[] {TEST_IP_ADDRESS_4}, TYPE_A); + mFakeDns.setAnswer( + "sos." + excludedFqdnFromConfig, new String[] {TEST_IP_ADDRESS_5}, TYPE_A); + mFakeDns.setAnswer(expectedFqdnFromRplmn, new String[] {TEST_IP_ADDRESS_6}, TYPE_A); + mFakeDns.setAnswer( + "sos." + expectedFqdnFromRplmn, new String[] {TEST_IP_ADDRESS_7}, TYPE_A); ArrayList<InetAddress> testInetAddresses = getValidatedServerListWithDefaultParams(isEmergency); - assertEquals(testInetAddresses.size(), 3); - assertEquals(testInetAddresses.get(0), InetAddress.getByName(TEST_IP_ADDRESS)); - assertEquals(testInetAddresses.get(1), InetAddress.getByName(TEST_IP_ADDRESS_2)); - assertEquals(testInetAddresses.get(2), InetAddress.getByName(TEST_IP_ADDRESS_1)); + if (isEmergency) { + assertEquals(6, testInetAddresses.size()); + assertEquals(InetAddress.getByName(TEST_IP_ADDRESS_7), testInetAddresses.get(0)); + assertEquals(InetAddress.getByName(TEST_IP_ADDRESS_6), testInetAddresses.get(1)); + assertEquals(InetAddress.getByName(TEST_IP_ADDRESS_3), testInetAddresses.get(2)); + assertEquals(InetAddress.getByName(TEST_IP_ADDRESS), testInetAddresses.get(3)); + assertEquals(InetAddress.getByName(TEST_IP_ADDRESS_4), testInetAddresses.get(4)); + assertEquals(InetAddress.getByName(TEST_IP_ADDRESS_1), testInetAddresses.get(5)); + } else { + assertEquals(3, testInetAddresses.size()); + assertEquals(InetAddress.getByName(TEST_IP_ADDRESS_6), testInetAddresses.get(0)); + assertEquals(InetAddress.getByName(TEST_IP_ADDRESS), testInetAddresses.get(1)); + assertEquals(InetAddress.getByName(TEST_IP_ADDRESS_1), testInetAddresses.get(2)); + } + } + + @Test + public void testPlmnResolutionMethodWithDuplicatedImsiAndEhplmn() throws Exception { + when(DnsResolver.getInstance()).thenReturn(mMockDnsResolver); + doReturn(true).when(mEpdgSelector).hasIpv4Address(mMockNetwork); + doReturn(true).when(mEpdgSelector).hasIpv6Address(mMockNetwork); + + String fqdnFromEhplmn1 = "epdg.epc.mnc120.mcc300.pub.3gppnetwork.org"; + String fqdnFromEhplmn2AndImsi = "epdg.epc.mnc120.mcc311.pub.3gppnetwork.org"; + String fqdnFromEhplmn3 = "epdg.epc.mnc122.mcc300.pub.3gppnetwork.org"; + String fqdnFromEhplmn4 = "epdg.epc.mnc123.mcc300.pub.3gppnetwork.org"; + + when(mMockTelephonyManager.getNetworkOperator()).thenReturn("300121"); + ehplmnList.add("300122"); + ehplmnList.add("300123"); + + mTestBundle.putIntArray( + CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY, + new int[] {CarrierConfigManager.Iwlan.EPDG_ADDRESS_PLMN}); + mTestBundle.putIntArray( + CarrierConfigManager.Iwlan.KEY_EPDG_PLMN_PRIORITY_INT_ARRAY, + new int[] { + CarrierConfigManager.Iwlan.EPDG_PLMN_HPLMN, + CarrierConfigManager.Iwlan.EPDG_PLMN_EHPLMN_ALL, + }); + + mFakeDns.setAnswer(fqdnFromEhplmn1, new String[] {TEST_IP_ADDRESS}, TYPE_A); + mFakeDns.setAnswer(fqdnFromEhplmn2AndImsi, new String[] {TEST_IP_ADDRESS_1}, TYPE_A); + mFakeDns.setAnswer(fqdnFromEhplmn3, new String[] {TEST_IP_ADDRESS_2}, TYPE_A); + mFakeDns.setAnswer(fqdnFromEhplmn4, new String[] {TEST_IP_ADDRESS_3}, TYPE_A); + + ArrayList<InetAddress> testInetAddresses = getValidatedServerListWithDefaultParams(false); + + assertEquals(4, testInetAddresses.size()); + assertEquals(InetAddress.getByName(TEST_IP_ADDRESS_1), testInetAddresses.get(0)); + assertEquals(InetAddress.getByName(TEST_IP_ADDRESS), testInetAddresses.get(1)); + assertEquals(InetAddress.getByName(TEST_IP_ADDRESS_2), testInetAddresses.get(2)); + assertEquals(InetAddress.getByName(TEST_IP_ADDRESS_3), testInetAddresses.get(3)); + } + + @Test + public void testPlmnResolutionMethodWithInvalidLengthPlmns() throws Exception { + when(DnsResolver.getInstance()).thenReturn(mMockDnsResolver); + doReturn(true).when(mEpdgSelector).hasIpv4Address(mMockNetwork); + doReturn(true).when(mEpdgSelector).hasIpv6Address(mMockNetwork); + + when(mMockSubscriptionInfo.getMccString()).thenReturn("31"); + when(mMockSubscriptionInfo.getMncString()).thenReturn("12"); + + when(mMockTelephonyManager.getNetworkOperator()).thenReturn("300"); + ehplmnList.add("3001"); + ehplmnList.add("3"); + + mTestBundle.putIntArray( + CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY, + new int[] {CarrierConfigManager.Iwlan.EPDG_ADDRESS_PLMN}); + mTestBundle.putIntArray( + CarrierConfigManager.Iwlan.KEY_EPDG_PLMN_PRIORITY_INT_ARRAY, + new int[] { + CarrierConfigManager.Iwlan.EPDG_PLMN_RPLMN, + CarrierConfigManager.Iwlan.EPDG_PLMN_HPLMN, + CarrierConfigManager.Iwlan.EPDG_PLMN_EHPLMN_ALL, + }); + + ArrayList<InetAddress> testInetAddresses = getValidatedServerListWithDefaultParams(false); + + assertEquals(0, testInetAddresses.size()); + } + + @Test + public void testPlmnResolutionMethodWithInvalidCharacterPlmns() throws Exception { + when(DnsResolver.getInstance()).thenReturn(mMockDnsResolver); + doReturn(true).when(mEpdgSelector).hasIpv4Address(mMockNetwork); + doReturn(true).when(mEpdgSelector).hasIpv6Address(mMockNetwork); + + when(mMockSubscriptionInfo.getMccString()).thenReturn("a b"); + when(mMockSubscriptionInfo.getMncString()).thenReturn("!@#"); + + when(mMockTelephonyManager.getNetworkOperator()).thenReturn("a cde#"); + ehplmnList.add("abcdef"); + ehplmnList.add("1 23456"); + ehplmnList.add("1 2345"); + + mTestBundle.putIntArray( + CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY, + new int[] {CarrierConfigManager.Iwlan.EPDG_ADDRESS_PLMN}); + mTestBundle.putIntArray( + CarrierConfigManager.Iwlan.KEY_EPDG_PLMN_PRIORITY_INT_ARRAY, + new int[] { + CarrierConfigManager.Iwlan.EPDG_PLMN_RPLMN, + CarrierConfigManager.Iwlan.EPDG_PLMN_HPLMN, + CarrierConfigManager.Iwlan.EPDG_PLMN_EHPLMN_ALL, + }); + + ArrayList<InetAddress> testInetAddresses = getValidatedServerListWithDefaultParams(false); + + assertEquals(0, testInetAddresses.size()); + } + + @Test + public void testPlmnResolutionMethodWithEmptyPlmns() throws Exception { + when(DnsResolver.getInstance()).thenReturn(mMockDnsResolver); + doReturn(true).when(mEpdgSelector).hasIpv4Address(mMockNetwork); + doReturn(true).when(mEpdgSelector).hasIpv6Address(mMockNetwork); + + when(mMockSubscriptionInfo.getMccString()).thenReturn(null); + when(mMockSubscriptionInfo.getMncString()).thenReturn(null); + + when(mMockTelephonyManager.getNetworkOperator()).thenReturn(""); + ehplmnList.add(""); + + mTestBundle.putIntArray( + CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY, + new int[] {CarrierConfigManager.Iwlan.EPDG_ADDRESS_PLMN}); + mTestBundle.putIntArray( + CarrierConfigManager.Iwlan.KEY_EPDG_PLMN_PRIORITY_INT_ARRAY, + new int[] { + CarrierConfigManager.Iwlan.EPDG_PLMN_RPLMN, + CarrierConfigManager.Iwlan.EPDG_PLMN_HPLMN, + CarrierConfigManager.Iwlan.EPDG_PLMN_EHPLMN_ALL, + }); + + ArrayList<InetAddress> testInetAddresses = getValidatedServerListWithDefaultParams(false); + + assertEquals(0, testInetAddresses.size()); + } + + @Test + public void testPlmnResolutionMethodWithFirstEhplmn() throws Exception { + when(DnsResolver.getInstance()).thenReturn(mMockDnsResolver); + doReturn(true).when(mEpdgSelector).hasIpv4Address(mMockNetwork); + doReturn(true).when(mEpdgSelector).hasIpv6Address(mMockNetwork); + + String fqdnFromEhplmn1 = "epdg.epc.mnc120.mcc300.pub.3gppnetwork.org"; + String fqdnFromEhplmn2 = "epdg.epc.mnc121.mcc300.pub.3gppnetwork.org"; + String fqdnFromEhplmn3 = "epdg.epc.mnc122.mcc300.pub.3gppnetwork.org"; + String fqdnFromEhplmn4 = "epdg.epc.mnc123.mcc300.pub.3gppnetwork.org"; + + ehplmnList.add("300121"); + ehplmnList.add("300122"); + ehplmnList.add("300123"); + + mTestBundle.putIntArray( + CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY, + new int[] {CarrierConfigManager.Iwlan.EPDG_ADDRESS_PLMN}); + mTestBundle.putIntArray( + CarrierConfigManager.Iwlan.KEY_EPDG_PLMN_PRIORITY_INT_ARRAY, + new int[] {CarrierConfigManager.Iwlan.EPDG_PLMN_EHPLMN_FIRST}); + + mFakeDns.setAnswer(fqdnFromEhplmn1, new String[] {TEST_IP_ADDRESS}, TYPE_A); + mFakeDns.setAnswer(fqdnFromEhplmn2, new String[] {TEST_IP_ADDRESS_1}, TYPE_A); + mFakeDns.setAnswer(fqdnFromEhplmn3, new String[] {TEST_IP_ADDRESS_2}, TYPE_A); + mFakeDns.setAnswer(fqdnFromEhplmn4, new String[] {TEST_IP_ADDRESS_3}, TYPE_A); + + ArrayList<InetAddress> testInetAddresses = getValidatedServerListWithDefaultParams(false); + + assertEquals(1, testInetAddresses.size()); + assertEquals(InetAddress.getByName(TEST_IP_ADDRESS), testInetAddresses.get(0)); + } + + @Test + public void testPlmnResolutionMethodWithRplmn() throws Exception { + 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"; + String fqdnFromEhplmn1 = "epdg.epc.mnc120.mcc300.pub.3gppnetwork.org"; + String fqdnFromEhplmn2 = "epdg.epc.mnc121.mcc300.pub.3gppnetwork.org"; + + when(mMockTelephonyManager.getNetworkOperator()).thenReturn("300122"); + ehplmnList.add("300121"); + + mTestBundle.putStringArray( + CarrierConfigManager.Iwlan.KEY_MCC_MNCS_STRING_ARRAY, + new String[] {"310-480", "300-122", "300-121"}); + + mTestBundle.putIntArray( + CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY, + new int[] {CarrierConfigManager.Iwlan.EPDG_ADDRESS_PLMN}); + mTestBundle.putIntArray( + CarrierConfigManager.Iwlan.KEY_EPDG_PLMN_PRIORITY_INT_ARRAY, + new int[] {CarrierConfigManager.Iwlan.EPDG_PLMN_RPLMN}); + + mFakeDns.setAnswer(fqdnFromRplmn, new String[] {TEST_IP_ADDRESS}, TYPE_A); + mFakeDns.setAnswer(fqdnFromEhplmn1, new String[] {TEST_IP_ADDRESS_1}, TYPE_A); + mFakeDns.setAnswer(fqdnFromEhplmn2, new String[] {TEST_IP_ADDRESS_2}, TYPE_A); + + ArrayList<InetAddress> testInetAddresses = getValidatedServerListWithDefaultParams(false); + + assertEquals(1, testInetAddresses.size()); + assertEquals(InetAddress.getByName(TEST_IP_ADDRESS), testInetAddresses.get(0)); } @Test public void testCarrierConfigStaticAddressList() throws Exception { - // Set Network.getAllByName mock + when(DnsResolver.getInstance()).thenReturn(mMockDnsResolver); + doReturn(true).when(mEpdgSelector).hasIpv4Address(mMockNetwork); + doReturn(true).when(mEpdgSelector).hasIpv6Address(mMockNetwork); + + // Set DnsResolver query mock final String addr1 = "epdg.epc.mnc480.mcc310.pub.3gppnetwork.org"; final String addr2 = "epdg.epc.mnc120.mcc300.pub.3gppnetwork.org"; final String addr3 = "epdg.epc.mnc120.mcc311.pub.3gppnetwork.org"; @@ -284,27 +542,37 @@ public class EpdgSelectorTest { ArrayList<InetAddress> testInetAddresses = getValidatedServerListWithDefaultParams(false /*isEmergency*/); - assertEquals(testInetAddresses.size(), 3); - assertEquals(testInetAddresses.get(0), InetAddress.getByName(TEST_IP_ADDRESS_1)); - assertEquals(testInetAddresses.get(1), InetAddress.getByName(TEST_IP_ADDRESS_2)); - assertEquals(testInetAddresses.get(2), InetAddress.getByName(TEST_IP_ADDRESS)); + assertEquals(3, testInetAddresses.size()); + assertEquals(InetAddress.getByName(TEST_IP_ADDRESS_1), testInetAddresses.get(0)); + assertEquals(InetAddress.getByName(TEST_IP_ADDRESS_2), testInetAddresses.get(1)); + assertEquals(InetAddress.getByName(TEST_IP_ADDRESS), testInetAddresses.get(2)); } private ArrayList<InetAddress> getValidatedServerListWithDefaultParams(boolean isEmergency) throws Exception { + return getValidatedServerListWithIpPreference( + EpdgSelector.PROTO_FILTER_IPV4V6, EpdgSelector.IPV4_PREFERRED, isEmergency); + } + + private ArrayList<InetAddress> getValidatedServerListWithIpPreference( + @EpdgSelector.ProtoFilter int filter, + @EpdgSelector.EpdgAddressOrder int order, + boolean isEmergency) + throws Exception { ArrayList<InetAddress> testInetAddresses = new ArrayList<InetAddress>(); final CountDownLatch latch = new CountDownLatch(1); IwlanError ret = mEpdgSelector.getValidatedServerList( 1234, - EpdgSelector.PROTO_FILTER_IPV4V6, - false /*isRoaming*/, + filter, + order, + false /* isRoaming */, isEmergency, mMockNetwork, new EpdgSelector.EpdgSelectorCallback() { @Override public void onServerListChanged( - int transactionId, ArrayList<InetAddress> validIPList) { + int transactionId, List<InetAddress> validIPList) { assertEquals(transactionId, 1234); for (InetAddress mInetAddress : validIPList) { @@ -357,7 +625,7 @@ public class EpdgSelectorTest { ArrayList<InetAddress> testInetAddresses = getValidatedServerListWithDefaultParams(false /* isEmergency */); - assertEquals(testInetAddresses.size(), 2); + assertEquals(2, testInetAddresses.size()); assertTrue(testInetAddresses.contains(InetAddress.getByName(TEST_IP_ADDRESS))); assertTrue(testInetAddresses.contains(InetAddress.getByName(TEST_IPV6_ADDRESS))); } @@ -378,6 +646,8 @@ public class EpdgSelectorTest { } private void testCellularResolutionMethod(boolean isEmergency) throws Exception { + when(DnsResolver.getInstance()).thenReturn(mMockDnsResolver); + int testMcc = 311; int testMnc = 120; String testMccString = "311"; @@ -429,10 +699,10 @@ public class EpdgSelectorTest { ArrayList<InetAddress> testInetAddresses = getValidatedServerListWithDefaultParams(isEmergency); - assertEquals(testInetAddresses.size(), 3); - assertEquals(testInetAddresses.get(0), InetAddress.getByName(TEST_IP_ADDRESS)); - assertEquals(testInetAddresses.get(1), InetAddress.getByName(TEST_IP_ADDRESS_1)); - assertEquals(testInetAddresses.get(2), InetAddress.getByName(TEST_IP_ADDRESS_2)); + assertEquals(3, testInetAddresses.size()); + assertEquals(InetAddress.getByName(TEST_IP_ADDRESS), testInetAddresses.get(0)); + assertEquals(InetAddress.getByName(TEST_IP_ADDRESS_1), testInetAddresses.get(1)); + assertEquals(InetAddress.getByName(TEST_IP_ADDRESS_2), testInetAddresses.get(2)); } private void setAnswerForCellularMethod(boolean isEmergency, int mcc, int mnc) @@ -471,6 +741,197 @@ public class EpdgSelectorTest { mFakeDns.setAnswer(expectedFqdn3, new String[] {TEST_IP_ADDRESS_2}, TYPE_A); } + @Test + public void testGetValidatedServerListIpv4Preferred() throws Exception { + when(DnsResolver.getInstance()).thenReturn(mMockDnsResolver); + doReturn(true).when(mEpdgSelector).hasIpv4Address(mMockNetwork); + doReturn(true).when(mEpdgSelector).hasIpv6Address(mMockNetwork); + + final String addr1 = "epdg.epc.mnc120.mcc300.pub.3gppnetwork.org"; + final String addr2 = "epdg.epc.mnc120.mcc311.pub.3gppnetwork.org"; + final String testStaticAddress = addr1 + "," + addr2; + + mFakeDns.setAnswer(addr1, new String[] {TEST_IP_ADDRESS_1}, TYPE_A); + mFakeDns.setAnswer(addr2, new String[] {TEST_IPV6_ADDRESS}, TYPE_AAAA); + + // Set carrier config mock + mTestBundle.putIntArray( + CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY, + new int[] {CarrierConfigManager.Iwlan.EPDG_ADDRESS_STATIC}); + mTestBundle.putString( + CarrierConfigManager.Iwlan.KEY_EPDG_STATIC_ADDRESS_STRING, testStaticAddress); + + ArrayList<InetAddress> testInetAddresses = + getValidatedServerListWithIpPreference( + EpdgSelector.PROTO_FILTER_IPV4V6, + EpdgSelector.IPV4_PREFERRED, + false /*isEmergency*/); + + assertEquals(2, testInetAddresses.size()); + assertEquals(InetAddress.getByName(TEST_IP_ADDRESS_1), testInetAddresses.get(0)); + assertEquals(InetAddress.getByName(TEST_IPV6_ADDRESS), testInetAddresses.get(1)); + } + + @Test + public void testGetValidatedServerListIpv6Preferred() throws Exception { + when(DnsResolver.getInstance()).thenReturn(mMockDnsResolver); + doReturn(true).when(mEpdgSelector).hasIpv4Address(mMockNetwork); + doReturn(true).when(mEpdgSelector).hasIpv6Address(mMockNetwork); + + final String addr1 = "epdg.epc.mnc120.mcc300.pub.3gppnetwork.org"; + final String addr2 = "epdg.epc.mnc120.mcc311.pub.3gppnetwork.org"; + final String testStaticAddress = addr1 + "," + addr2; + + mFakeDns.setAnswer(addr1, new String[] {TEST_IP_ADDRESS_1}, TYPE_A); + mFakeDns.setAnswer(addr2, new String[] {TEST_IPV6_ADDRESS}, TYPE_AAAA); + + // Set carrier config mock + mTestBundle.putIntArray( + CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY, + new int[] {CarrierConfigManager.Iwlan.EPDG_ADDRESS_STATIC}); + mTestBundle.putString( + CarrierConfigManager.Iwlan.KEY_EPDG_STATIC_ADDRESS_STRING, testStaticAddress); + + ArrayList<InetAddress> testInetAddresses = + getValidatedServerListWithIpPreference( + EpdgSelector.PROTO_FILTER_IPV4V6, + EpdgSelector.IPV6_PREFERRED, + false /*isEmergency*/); + + assertEquals(2, testInetAddresses.size()); + assertEquals(InetAddress.getByName(TEST_IPV6_ADDRESS), testInetAddresses.get(0)); + assertEquals(InetAddress.getByName(TEST_IP_ADDRESS_1), testInetAddresses.get(1)); + } + + @Test + public void testGetValidatedServerListIpv4Only() throws Exception { + when(DnsResolver.getInstance()).thenReturn(mMockDnsResolver); + doReturn(true).when(mEpdgSelector).hasIpv4Address(mMockNetwork); + doReturn(true).when(mEpdgSelector).hasIpv6Address(mMockNetwork); + + final String addr1 = "epdg.epc.mnc120.mcc300.pub.3gppnetwork.org"; + final String addr2 = "epdg.epc.mnc120.mcc311.pub.3gppnetwork.org"; + final String testStaticAddress = addr1 + "," + addr2; + + mFakeDns.setAnswer(addr1, new String[] {TEST_IP_ADDRESS_1}, TYPE_A); + mFakeDns.setAnswer(addr2, new String[] {TEST_IPV6_ADDRESS}, TYPE_AAAA); + + // Set carrier config mock + mTestBundle.putIntArray( + CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY, + new int[] {CarrierConfigManager.Iwlan.EPDG_ADDRESS_STATIC}); + mTestBundle.putString( + CarrierConfigManager.Iwlan.KEY_EPDG_STATIC_ADDRESS_STRING, testStaticAddress); + + ArrayList<InetAddress> testInetAddresses = + getValidatedServerListWithIpPreference( + EpdgSelector.PROTO_FILTER_IPV4, + EpdgSelector.SYSTEM_PREFERRED, + false /*isEmergency*/); + + assertEquals(1, testInetAddresses.size()); + assertEquals(InetAddress.getByName(TEST_IP_ADDRESS_1), testInetAddresses.get(0)); + } + + @Test + public void testGetValidatedServerListIpv4OnlyCongestion() throws Exception { + when(DnsResolver.getInstance()).thenReturn(mMockDnsResolver); + doReturn(true).when(mEpdgSelector).hasIpv4Address(mMockNetwork); + doReturn(true).when(mEpdgSelector).hasIpv6Address(mMockNetwork); + + when(mMockErrorPolicyManager.getMostRecentDataFailCause()) + .thenReturn(DataFailCause.IWLAN_CONGESTION); + when(mMockErrorPolicyManager.getCurrentFqdnIndex(anyInt())).thenReturn(0); + + String expectedFqdnFromHplmn = "epdg.epc.mnc120.mcc311.pub.3gppnetwork.org"; + String expectedFqdnFromEHplmn = "epdg.epc.mnc120.mcc300.pub.3gppnetwork.org"; + String expectedFqdnFromConfig = "epdg.epc.mnc480.mcc310.pub.3gppnetwork.org"; + + mTestBundle.putIntArray( + CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY, + new int[] {CarrierConfigManager.Iwlan.EPDG_ADDRESS_PLMN}); + mTestBundle.putStringArray( + CarrierConfigManager.Iwlan.KEY_MCC_MNCS_STRING_ARRAY, + new String[] {"310-480", "300-120", "311-120"}); + + mFakeDns.setAnswer(expectedFqdnFromHplmn, new String[] {TEST_IPV6_ADDRESS}, TYPE_AAAA); + mFakeDns.setAnswer(expectedFqdnFromEHplmn, new String[] {TEST_IP_ADDRESS_1}, TYPE_A); + mFakeDns.setAnswer(expectedFqdnFromConfig, new String[] {TEST_IP_ADDRESS_2}, TYPE_A); + + ArrayList<InetAddress> testInetAddresses = + getValidatedServerListWithIpPreference( + EpdgSelector.PROTO_FILTER_IPV4, + EpdgSelector.SYSTEM_PREFERRED, + false /*isEmergency*/); + + assertEquals(1, testInetAddresses.size()); + assertEquals(InetAddress.getByName(TEST_IP_ADDRESS_1), testInetAddresses.get(0)); + } + + @Test + public void testGetValidatedServerListIpv6Only() throws Exception { + when(DnsResolver.getInstance()).thenReturn(mMockDnsResolver); + doReturn(true).when(mEpdgSelector).hasIpv4Address(mMockNetwork); + doReturn(true).when(mEpdgSelector).hasIpv6Address(mMockNetwork); + + final String addr1 = "epdg.epc.mnc120.mcc300.pub.3gppnetwork.org"; + final String addr2 = "epdg.epc.mnc120.mcc311.pub.3gppnetwork.org"; + final String testStaticAddress = addr1 + "," + addr2; + + mFakeDns.setAnswer(addr1, new String[] {TEST_IP_ADDRESS_1}, TYPE_A); + mFakeDns.setAnswer(addr2, new String[] {TEST_IPV6_ADDRESS}, TYPE_AAAA); + + // Set carrier config mock + mTestBundle.putIntArray( + CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY, + new int[] {CarrierConfigManager.Iwlan.EPDG_ADDRESS_STATIC}); + mTestBundle.putString( + CarrierConfigManager.Iwlan.KEY_EPDG_STATIC_ADDRESS_STRING, testStaticAddress); + + ArrayList<InetAddress> testInetAddresses = + getValidatedServerListWithIpPreference( + EpdgSelector.PROTO_FILTER_IPV6, + EpdgSelector.SYSTEM_PREFERRED, + false /*isEmergency*/); + + assertEquals(1, testInetAddresses.size()); + assertEquals(InetAddress.getByName(TEST_IPV6_ADDRESS), testInetAddresses.get(0)); + } + + @Test + public void testGetValidatedServerListSystemPreferred() throws Exception { + when(DnsResolver.getInstance()).thenReturn(mMockDnsResolver); + doReturn(true).when(mEpdgSelector).hasIpv4Address(mMockNetwork); + doReturn(true).when(mEpdgSelector).hasIpv6Address(mMockNetwork); + + final String addr1 = "epdg.epc.mnc120.mcc300.pub.3gppnetwork.org"; + final String addr2 = "epdg.epc.mnc120.mcc311.pub.3gppnetwork.org"; + final String addr3 = "epdg.epc.mnc120.mcc312.pub.3gppnetwork.org"; + final String testStaticAddress = addr1 + "," + addr2 + "," + addr3; + + mFakeDns.setAnswer(addr1, new String[] {TEST_IP_ADDRESS_1}, TYPE_A); + mFakeDns.setAnswer(addr2, new String[] {TEST_IPV6_ADDRESS}, TYPE_AAAA); + mFakeDns.setAnswer(addr3, new String[] {TEST_IP_ADDRESS_2}, TYPE_A); + + // Set carrier config mock + mTestBundle.putIntArray( + CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY, + new int[] {CarrierConfigManager.Iwlan.EPDG_ADDRESS_STATIC}); + mTestBundle.putString( + CarrierConfigManager.Iwlan.KEY_EPDG_STATIC_ADDRESS_STRING, testStaticAddress); + + ArrayList<InetAddress> testInetAddresses = + getValidatedServerListWithIpPreference( + EpdgSelector.PROTO_FILTER_IPV4V6, + EpdgSelector.SYSTEM_PREFERRED, + false /*isEmergency*/); + + assertEquals(3, testInetAddresses.size()); + assertEquals(InetAddress.getByName(TEST_IP_ADDRESS_1), testInetAddresses.get(0)); + assertEquals(InetAddress.getByName(TEST_IPV6_ADDRESS), testInetAddresses.get(1)); + assertEquals(InetAddress.getByName(TEST_IP_ADDRESS_2), testInetAddresses.get(2)); + } + /** * Fakes DNS responses. * @@ -492,24 +953,24 @@ public class EpdgSelectorTest { // Full match or partial match that target host contains the entry hostname to support // random private dns probe hostname. private boolean matches(String hostname, int type) { - return hostname.endsWith(mHostname) && type == mType; + return hostname.equals(mHostname) && type == mType; } } - private final ArrayList<DnsEntry> mAnswers = new ArrayList<DnsEntry>(); + private final List<DnsEntry> mAnswers = new ArrayList<>(); /** Clears all DNS entries. */ private synchronized void clearAll() { mAnswers.clear(); } - /** Returns the answer for a given name and type on the given mock network. */ - private synchronized List<InetAddress> getAnswer(Object mock, String hostname, int type) { + /** Returns the answer for a given name and type. */ + private synchronized List<InetAddress> getAnswer(String hostname, int type) { return mAnswers.stream() .filter(e -> e.matches(hostname, type)) .map(answer -> answer.mAddresses) .findFirst() - .orElse(null); + .orElse(List.of()); } /** Sets the answer for a given name and type. */ @@ -530,10 +991,20 @@ public class EpdgSelectorTest { } // Regardless of the type, depends on what the responses contained in the network. - private List<InetAddress> queryAllTypes(Object mock, String hostname) { + private List<InetAddress> queryIpv4(String hostname) { + return getAnswer(hostname, TYPE_A); + } + + // Regardless of the type, depends on what the responses contained in the network. + private List<InetAddress> queryIpv6(String hostname) { + return getAnswer(hostname, TYPE_AAAA); + } + + // Regardless of the type, depends on what the responses contained in the network. + private List<InetAddress> queryAllTypes(String hostname) { List<InetAddress> answer = new ArrayList<>(); - addAllIfNotNull(answer, getAnswer(mock, hostname, TYPE_A)); - addAllIfNotNull(answer, getAnswer(mock, hostname, TYPE_AAAA)); + answer.addAll(queryIpv4(hostname)); + answer.addAll(queryIpv6(hostname)); return answer; } @@ -545,32 +1016,55 @@ public class EpdgSelectorTest { /** Starts mocking DNS queries. */ private void startMocking() throws UnknownHostException { + // 5-arg DnsResolver.query() doAnswer( invocation -> { return mockQuery( invocation, 1 /* posHostname */, + -1 /* posType */, 3 /* posExecutor */, - 5 /* posCallback */, - -1 /* posType */); + 5 /* posCallback */); }) .when(mMockDnsResolver) - .query(any(), any(), anyInt(), any(), any(), any()); + .query(any(), anyString(), anyInt(), any(), any(), any()); + + // 6-arg DnsResolver.query() with explicit query type (IPv4 or v6). + doAnswer( + invocation -> { + return mockQuery( + invocation, + 1 /* posHostname */, + 2 /* posType */, + 4 /* posExecutor */, + 6 /* posCallback */); + }) + .when(mMockDnsResolver) + .query(any(), anyString(), anyInt(), anyInt(), any(), any(), any()); } // Mocking queries on DnsResolver#query. private Answer mockQuery( InvocationOnMock invocation, int posHostname, + int posType, int posExecutor, - int posCallback, - int posType) { - String hostname = (String) invocation.getArgument(posHostname); - Executor executor = (Executor) invocation.getArgument(posExecutor); + int posCallback) { + String hostname = invocation.getArgument(posHostname); + Executor executor = invocation.getArgument(posExecutor); DnsResolver.Callback<List<InetAddress>> callback = invocation.getArgument(posCallback); List<InetAddress> answer; - answer = queryAllTypes(invocation.getMock(), hostname); + switch (posType) { + case TYPE_A: + answer = queryIpv4(hostname); + break; + case TYPE_AAAA: + answer = queryIpv6(hostname); + break; + default: + answer = queryAllTypes(hostname); + } if (answer != null && answer.size() > 0) { new Handler(Looper.getMainLooper()) diff --git a/test/com/google/android/iwlan/epdg/EpdgTunnelManagerTest.java b/test/com/google/android/iwlan/epdg/EpdgTunnelManagerTest.java index f2cba73..76ceec8 100644 --- a/test/com/google/android/iwlan/epdg/EpdgTunnelManagerTest.java +++ b/test/com/google/android/iwlan/epdg/EpdgTunnelManagerTest.java @@ -19,11 +19,23 @@ package com.google.android.iwlan.epdg; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyLong; import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -47,6 +59,7 @@ import android.net.ipsec.ike.IkeSessionParams; import android.net.ipsec.ike.SaProposal; import android.net.ipsec.ike.TunnelModeChildSessionParams; import android.net.ipsec.ike.exceptions.IkeException; +import android.net.ipsec.ike.exceptions.IkeIOException; import android.net.ipsec.ike.exceptions.IkeInternalException; import android.net.ipsec.ike.exceptions.IkeProtocolException; import android.net.ipsec.ike.ike3gpp.Ike3gppBackoffTimer; @@ -61,8 +74,12 @@ import android.telephony.TelephonyManager; import android.telephony.data.ApnSetting; import com.google.android.iwlan.IwlanError; +import com.google.android.iwlan.IwlanTunnelMetricsImpl; +import com.google.android.iwlan.TunnelMetricsInterface.OnClosedMetrics; +import com.google.android.iwlan.TunnelMetricsInterface.OnOpenedMetrics; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -78,7 +95,6 @@ import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.concurrent.Executor; @@ -86,41 +102,30 @@ import java.util.concurrent.Executor; public class EpdgTunnelManagerTest { public static final int DEFAULT_SLOT_INDEX = 0; public static final int DEFAULT_SUBID = 0; + public static final int DEFAULT_TOKEN = 0; private static final String EPDG_ADDRESS = "127.0.0.1"; + private static final String EPDG_ADDRESS_IPV6 = "2600:387:f:707::1"; private static final String TEST_APN_NAME = "www.xyz.com"; - private static final ArrayList<InetAddress> EXPECTED_LOCAL_ADDRESSES = - new ArrayList<>( - Arrays.asList( - new InetAddress[] {InetAddresses.parseNumericAddress("201.1.100.10")})); - private static final ArrayList<InetAddress> EXPECTED_EPDG_ADDRESSES = - new ArrayList<>( - Arrays.asList( - new InetAddress[] {InetAddresses.parseNumericAddress(EPDG_ADDRESS)})); - private static final ArrayList<LinkAddress> EXPECTED_INTERNAL_ADDRESSES = - new ArrayList<>( - Arrays.asList( - new LinkAddress[] { - new LinkAddress( - InetAddresses.parseNumericAddress("198.50.100.10"), 24) - })); - private static final ArrayList<InetAddress> EXPECTED_PCSCF_ADDRESSES = - new ArrayList<>( - Arrays.asList( - new InetAddress[] { - InetAddresses.parseNumericAddress("198.51.100.10") - })); - private static final ArrayList<InetAddress> EXPECTED_DNS_ADDRESSES = - new ArrayList<>( - Arrays.asList( - new InetAddress[] { - InetAddresses.parseNumericAddress("198.50.100.10") - })); + private static final List<InetAddress> EXPECTED_LOCAL_ADDRESSES = + List.of(InetAddresses.parseNumericAddress("201.1.100.10")); + private static final List<InetAddress> EXPECTED_IPV6_LOCAL_ADDRESSES = + List.of(InetAddresses.parseNumericAddress("2001:db8::1:2")); + private static final List<InetAddress> EXPECTED_EPDG_ADDRESSES = + List.of(InetAddresses.parseNumericAddress(EPDG_ADDRESS)); + private static final List<InetAddress> EXPECTED_EPDG_ADDRESSES_IPV6 = + List.of(InetAddresses.parseNumericAddress(EPDG_ADDRESS_IPV6)); + private static final List<LinkAddress> EXPECTED_INTERNAL_ADDRESSES = + List.of(new LinkAddress(InetAddresses.parseNumericAddress("198.50.100.10"), 24)); + private static final List<InetAddress> EXPECTED_PCSCF_ADDRESSES = + List.of(InetAddresses.parseNumericAddress("198.51.100.10")); + private static final List<InetAddress> EXPECTED_DNS_ADDRESSES = + List.of(InetAddresses.parseNumericAddress("198.50.100.10")); private EpdgTunnelManager mEpdgTunnelManager; - private class IwlanTunnelCallback implements EpdgTunnelManager.TunnelCallback { + private static class IwlanTunnelCallback implements EpdgTunnelManager.TunnelCallback { public void onOpened(String apnName, TunnelLinkProperties linkProperties) {} public void onClosed(String apnName, IwlanError error) {} @@ -130,10 +135,11 @@ public class EpdgTunnelManagerTest { private TestLooper mTestLooper = new TestLooper(); @Mock private Context mMockContext; + @Mock private Network mMockDefaultNetwork; @Mock private IwlanTunnelCallback mMockIwlanTunnelCallback; + @Mock private IwlanTunnelMetricsImpl mMockIwlanTunnelMetrics; @Mock private IkeSession mMockIkeSession; @Mock private EpdgSelector mMockEpdgSelector; - @Mock private Network mMockNetwork; @Mock CarrierConfigManager mMockCarrierConfigManager; @Mock ConnectivityManager mMockConnectivityManager; @Mock SubscriptionManager mMockSubscriptionManager; @@ -142,6 +148,7 @@ public class EpdgTunnelManagerTest { @Mock IpSecManager mMockIpSecManager; @Mock EpdgTunnelManager.IkeSessionCreator mMockIkeSessionCreator; @Mock IkeException mMockIkeException; + @Mock IkeIOException mMockIkeIoException; @Mock IkeSessionConfiguration mMockIkeSessionConfiguration; @Mock ChildSessionConfiguration mMockChildSessionConfiguration; @Mock IpSecManager.IpSecTunnelInterface mMockIpSecTunnelInterface; @@ -150,10 +157,20 @@ public class EpdgTunnelManagerTest { @Mock IpSecTransform mMockedIpSecTransformOut; @Mock LinkProperties mMockLinkProperties; - ArgumentCaptor<ChildSessionCallback> mChildSessionCallbackCaptor; + static class IkeSessionArgumentCaptors { + ArgumentCaptor<IkeSessionParams> mIkeSessionParamsCaptor = + ArgumentCaptor.forClass(IkeSessionParams.class); + ArgumentCaptor<ChildSessionParams> mChildSessionParamsCaptor = + ArgumentCaptor.forClass(ChildSessionParams.class); + ArgumentCaptor<IkeSessionCallback> mIkeSessionCallbackCaptor = + ArgumentCaptor.forClass(IkeSessionCallback.class); + ArgumentCaptor<ChildSessionCallback> mChildSessionCallbackCaptor = + ArgumentCaptor.forClass(ChildSessionCallback.class); + } @Before public void setUp() throws Exception { + EpdgTunnelManager.resetAllInstances(); mEpdgTunnelManager = spy(EpdgTunnelManager.getInstance(mMockContext, DEFAULT_SLOT_INDEX)); when(mMockContext.getSystemService(eq(IpSecManager.class))).thenReturn(mMockIpSecManager); @@ -161,16 +178,16 @@ public class EpdgTunnelManagerTest { doReturn(mTestLooper.getLooper()).when(mEpdgTunnelManager).getLooper(); setVariable(mEpdgTunnelManager, "mContext", mMockContext); mEpdgTunnelManager.initHandler(); - mEpdgTunnelManager.resetTunnelManagerState(); - when(mEpdgTunnelManager.getEpdgSelector()).thenReturn(mMockEpdgSelector); + doReturn(mMockEpdgSelector).when(mEpdgTunnelManager).getEpdgSelector(); when(mEpdgTunnelManager.getIkeSessionCreator()).thenReturn(mMockIkeSessionCreator); when(mMockEpdgSelector.getValidatedServerList( anyInt(), anyInt(), + anyInt(), anyBoolean(), anyBoolean(), - eq(mMockNetwork), + any(Network.class), any(EpdgSelector.EpdgSelectorCallback.class))) .thenReturn(new IwlanError(IwlanError.NO_ERROR)); @@ -185,18 +202,20 @@ public class EpdgTunnelManagerTest { when(mMockChildSessionConfiguration.getInternalAddresses()) .thenReturn(EXPECTED_INTERNAL_ADDRESSES); - when(mMockIpSecManager.createIpSecTunnelInterface(any(), any(), any())) + when(mMockIpSecManager.createIpSecTunnelInterface( + any(InetAddress.class), any(InetAddress.class), any(Network.class))) .thenReturn(mMockIpSecTunnelInterface); + when(mMockIpSecTunnelInterface.getInterfaceName()).thenReturn("ipsec10"); - when(mMockIpSecTunnelInterface.getInterfaceName()).thenReturn("wlan0"); - - when(mMockIkeSessionConnectionInfo.getNetwork()).thenReturn(mMockNetwork); - - mChildSessionCallbackCaptor = ArgumentCaptor.forClass(ChildSessionCallback.class); + when(mMockIkeSessionConnectionInfo.getNetwork()).thenReturn(mMockDefaultNetwork); doReturn(EXPECTED_LOCAL_ADDRESSES) .when(mEpdgTunnelManager) .getAddressForNetwork(any(), any()); + + when(mMockLinkProperties.isReachable(any())).thenReturn(true); + mEpdgTunnelManager.updateNetwork(mMockDefaultNetwork, mMockLinkProperties); + mTestLooper.dispatchAll(); } @Test @@ -204,7 +223,8 @@ public class EpdgTunnelManagerTest { boolean ret = mEpdgTunnelManager.bringUpTunnel( getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_PPP), - mMockIwlanTunnelCallback); + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics); assertFalse(ret); } @@ -213,13 +233,15 @@ public class EpdgTunnelManagerTest { boolean ret = mEpdgTunnelManager.bringUpTunnel( getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IPV6, 16), - mMockIwlanTunnelCallback); + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics); assertFalse(ret); ret = mEpdgTunnelManager.bringUpTunnel( getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IPV6, -1), - mMockIwlanTunnelCallback); + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics); assertFalse(ret); } @@ -242,13 +264,19 @@ public class EpdgTunnelManagerTest { doReturn(true).when(mEpdgTunnelManager).canBringUpTunnel(eq(testApnName2)); doReturn(true).when(mEpdgTunnelManager).canBringUpTunnel(eq(testApnName3)); - boolean ret = mEpdgTunnelManager.bringUpTunnel(TSR_v4, mMockIwlanTunnelCallback); + boolean ret = + mEpdgTunnelManager.bringUpTunnel( + TSR_v4, mMockIwlanTunnelCallback, mMockIwlanTunnelMetrics); assertTrue(ret); - ret = mEpdgTunnelManager.bringUpTunnel(TSR_v6, mMockIwlanTunnelCallback); + ret = + mEpdgTunnelManager.bringUpTunnel( + TSR_v6, mMockIwlanTunnelCallback, mMockIwlanTunnelMetrics); assertTrue(ret); - ret = mEpdgTunnelManager.bringUpTunnel(TSR_v4v6, mMockIwlanTunnelCallback); + ret = + mEpdgTunnelManager.bringUpTunnel( + TSR_v4v6, mMockIwlanTunnelCallback, mMockIwlanTunnelMetrics); assertTrue(ret); } @@ -259,7 +287,9 @@ public class EpdgTunnelManagerTest { when(mEpdgTunnelManager.getTunnelSetupRequestApnName(TSR)).thenReturn(null); - boolean ret = mEpdgTunnelManager.bringUpTunnel(TSR, mMockIwlanTunnelCallback); + boolean ret = + mEpdgTunnelManager.bringUpTunnel( + TSR, mMockIwlanTunnelCallback, mMockIwlanTunnelMetrics); assertFalse(ret); verify(mEpdgTunnelManager).getTunnelSetupRequestApnName(TSR); } @@ -270,7 +300,9 @@ public class EpdgTunnelManagerTest { when(mEpdgTunnelManager.isTunnelConfigContainExistApn(TEST_APN_NAME)).thenReturn(true); - boolean ret = mEpdgTunnelManager.bringUpTunnel(TSR, mMockIwlanTunnelCallback); + boolean ret = + mEpdgTunnelManager.bringUpTunnel( + TSR, mMockIwlanTunnelCallback, mMockIwlanTunnelMetrics); assertFalse(ret); verify(mEpdgTunnelManager).isTunnelConfigContainExistApn(TEST_APN_NAME); } @@ -282,10 +314,17 @@ public class EpdgTunnelManagerTest { TunnelSetupRequest TSR = getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP); mEpdgTunnelManager.putApnNameToTunnelConfig( - testApnName2, mMockIkeSession, mMockIwlanTunnelCallback, null, 0); + testApnName2, + mMockIkeSession, + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics, + null, + 0); doReturn(true).when(mEpdgTunnelManager).canBringUpTunnel(eq(TEST_APN_NAME)); - boolean ret = mEpdgTunnelManager.bringUpTunnel(TSR, mMockIwlanTunnelCallback); + boolean ret = + mEpdgTunnelManager.bringUpTunnel( + TSR, mMockIwlanTunnelCallback, mMockIwlanTunnelMetrics); assertTrue(ret); } @@ -298,21 +337,25 @@ public class EpdgTunnelManagerTest { PersistableBundle bundle = new PersistableBundle(); setupMockForGetConfig(bundle); - boolean ret = mEpdgTunnelManager.bringUpTunnel(TSR, mMockIwlanTunnelCallback); + boolean ret = + mEpdgTunnelManager.bringUpTunnel( + TSR, mMockIwlanTunnelCallback, mMockIwlanTunnelMetrics); assertTrue(ret); mTestLooper.dispatchAll(); verify(mMockEpdgSelector) .getValidatedServerList( anyInt(), - eq(EpdgSelector.PROTO_FILTER_IPV4), + eq(EpdgSelector.PROTO_FILTER_IPV4V6), + anyInt(), eq(false), eq(false), - eq(mMockNetwork), + eq(mMockDefaultNetwork), any()); } - private void setupTunnelBringup() throws Exception { + private void setupTunnelBringup( + String apnName, List<InetAddress> epdgAddresses, int transactionId) throws Exception { setupMockForGetConfig(null); doReturn(null) .when(mMockIkeSessionCreator) @@ -324,21 +367,27 @@ public class EpdgTunnelManagerTest { any(IkeSessionCallback.class), any(ChildSessionCallback.class)); - doReturn(true).when(mEpdgTunnelManager).canBringUpTunnel(eq(TEST_APN_NAME)); + doReturn(true).when(mEpdgTunnelManager).canBringUpTunnel(eq(apnName)); boolean ret = mEpdgTunnelManager.bringUpTunnel( - getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP), - mMockIwlanTunnelCallback); + getBasicTunnelSetupRequest(apnName, ApnSetting.PROTOCOL_IP), + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics); assertTrue(ret); mTestLooper.dispatchAll(); mEpdgTunnelManager.sendSelectionRequestComplete( - EXPECTED_EPDG_ADDRESSES, new IwlanError(IwlanError.NO_ERROR), 1); + epdgAddresses, new IwlanError(IwlanError.NO_ERROR), transactionId); mTestLooper.dispatchAll(); } + private void setupTunnelBringup() throws Exception { + setupTunnelBringup(TEST_APN_NAME, EXPECTED_EPDG_ADDRESSES, 1 /* transactionId */); + } + @Test + @Ignore("b/239753287- Telus carrier errors out on parsing DEVICE_IDENTITY response") public void testBringUpTunnelSetsDeviceIdentityImeiSv() throws Exception { when(mMockContext.getSystemService(eq(TelephonyManager.class))) .thenReturn(mMockTelephonyManager); @@ -370,6 +419,7 @@ public class EpdgTunnelManagerTest { } @Test + @Ignore("b/239753287- Telus carrier errors out on parsing DEVICE_IDENTITY response") public void testBringUpTunnelSetsDeviceIdentityImei() throws Exception { when(mMockContext.getSystemService(eq(TelephonyManager.class))) .thenReturn(mMockTelephonyManager); @@ -399,6 +449,7 @@ public class EpdgTunnelManagerTest { } @Test + @Ignore("b/239753287- Telus carrier errors out on parsing DEVICE_IDENTITY response") public void testBringUpTunnelNoDeviceIdentityWhenImeiUnavailable() throws Exception { when(mMockContext.getSystemService(eq(TelephonyManager.class))) .thenReturn(mMockTelephonyManager); @@ -426,29 +477,25 @@ public class EpdgTunnelManagerTest { @Test public void testBringUpTunnelWithMobilityOptions() throws Exception { - doReturn(null) - .when(mMockIkeSessionCreator) + setupTunnelBringup(); + ArgumentCaptor<IkeSessionParams> ikeSessionParamsCaptor = + ArgumentCaptor.forClass(IkeSessionParams.class); + verify(mMockIkeSessionCreator, atLeastOnce()) .createIkeSession( eq(mMockContext), - any(IkeSessionParams.class), + ikeSessionParamsCaptor.capture(), any(ChildSessionParams.class), any(Executor.class), any(IkeSessionCallback.class), any(ChildSessionCallback.class)); + IkeSessionParams ikeSessionParams = ikeSessionParamsCaptor.getValue(); + assertTrue(ikeSessionParams.hasIkeOption(IkeSessionParams.IKE_OPTION_MOBIKE)); + assertTrue(ikeSessionParams.hasIkeOption(IkeSessionParams.IKE_OPTION_REKEY_MOBILITY)); + } - doReturn(true).when(mEpdgTunnelManager).canBringUpTunnel(eq(TEST_APN_NAME)); - - boolean ret = - mEpdgTunnelManager.bringUpTunnel( - getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP), - mMockIwlanTunnelCallback); - assertTrue(ret); - mTestLooper.dispatchAll(); - - mEpdgTunnelManager.sendSelectionRequestComplete( - EXPECTED_EPDG_ADDRESSES, new IwlanError(IwlanError.NO_ERROR), 1); - mTestLooper.dispatchAll(); - + @Test + public void testBringUpTunnelIpv6_verifyMobikeDisabled() throws Exception { + setupTunnelBringup(TEST_APN_NAME, EXPECTED_EPDG_ADDRESSES_IPV6, 1); ArgumentCaptor<IkeSessionParams> ikeSessionParamsCaptor = ArgumentCaptor.forClass(IkeSessionParams.class); verify(mMockIkeSessionCreator, atLeastOnce()) @@ -460,18 +507,57 @@ public class EpdgTunnelManagerTest { any(IkeSessionCallback.class), any(ChildSessionCallback.class)); IkeSessionParams ikeSessionParams = ikeSessionParamsCaptor.getValue(); - assertTrue(ikeSessionParams.hasIkeOption(IkeSessionParams.IKE_OPTION_MOBIKE)); assertTrue(ikeSessionParams.hasIkeOption(IkeSessionParams.IKE_OPTION_REKEY_MOBILITY)); + assertFalse(ikeSessionParams.hasIkeOption(IkeSessionParams.IKE_OPTION_MOBIKE)); + } + + @Test + public void testInitialContactForFirstTunnelOnly() throws Exception { + final String firstApnName = "ims"; + final String secondApnName = "mms"; + + IkeSessionArgumentCaptors firstTunnelArgumentCaptors = + verifyBringUpTunnelWithDnsQuery(firstApnName); + ChildSessionCallback firstCallback = + firstTunnelArgumentCaptors.mChildSessionCallbackCaptor.getValue(); + + IkeSessionArgumentCaptors secondTunnelArgumentCaptors = + verifyBringUpTunnel(secondApnName, true /* needPendingBringUpReq */); + verifyTunnelOnOpened(firstApnName, firstCallback); + + ChildSessionCallback secondCallback = + secondTunnelArgumentCaptors.mChildSessionCallbackCaptor.getValue(); + verifyTunnelOnOpened(secondApnName, secondCallback); + + IkeSessionParams firstTunnelParams = + firstTunnelArgumentCaptors.mIkeSessionParamsCaptor.getValue(); + IkeSessionParams secondTunnelParams = + secondTunnelArgumentCaptors.mIkeSessionParamsCaptor.getValue(); + assertTrue(firstTunnelParams.hasIkeOption(IkeSessionParams.IKE_OPTION_INITIAL_CONTACT)); + assertFalse(secondTunnelParams.hasIkeOption(IkeSessionParams.IKE_OPTION_INITIAL_CONTACT)); } @Test public void testCloseTunnelWithNoTunnelForApn() throws Exception { String testApnName = "www.xyz.com"; + doReturn(0L) + .when(mEpdgTunnelManager) + .reportIwlanError(eq(testApnName), eq(new IwlanError(IwlanError.TUNNEL_NOT_FOUND))); - boolean ret = mEpdgTunnelManager.closeTunnel(testApnName, false /*forceClose*/); - assertTrue(ret); + mEpdgTunnelManager.closeTunnel( + testApnName, + false /*forceClose*/, + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics); mTestLooper.dispatchAll(); + verify(mEpdgTunnelManager).closePendingRequestsForApn(eq(testApnName)); + verify(mMockIwlanTunnelCallback) + .onClosed(eq(testApnName), eq(new IwlanError(IwlanError.TUNNEL_NOT_FOUND))); + ArgumentCaptor<OnClosedMetrics> metricsCaptor = + ArgumentCaptor.forClass(OnClosedMetrics.class); + verify(mMockIwlanTunnelMetrics, times(1)).onClosed(metricsCaptor.capture()); + assertEquals(testApnName, metricsCaptor.getValue().getApnName()); } @Test @@ -479,10 +565,18 @@ public class EpdgTunnelManagerTest { String testApnName = "www.xyz.com"; mEpdgTunnelManager.putApnNameToTunnelConfig( - testApnName, mMockIkeSession, mMockIwlanTunnelCallback, null, 0); - - boolean ret = mEpdgTunnelManager.closeTunnel(testApnName, true /*forceClose*/); - assertTrue(ret); + testApnName, + mMockIkeSession, + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics, + null, + 0); + + mEpdgTunnelManager.closeTunnel( + testApnName, + true /*forceClose*/, + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics); mTestLooper.dispatchAll(); verify(mMockIkeSession).kill(); @@ -494,10 +588,18 @@ public class EpdgTunnelManagerTest { String testApnName = "www.xyz.com"; mEpdgTunnelManager.putApnNameToTunnelConfig( - testApnName, mMockIkeSession, mMockIwlanTunnelCallback, null, 0); - - boolean ret = mEpdgTunnelManager.closeTunnel(testApnName, false /*forceClose*/); - assertTrue(ret); + testApnName, + mMockIkeSession, + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics, + null, + 0); + + mEpdgTunnelManager.closeTunnel( + testApnName, + false /*forceClose*/, + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics); mTestLooper.dispatchAll(); verify(mMockIkeSession).close(); @@ -526,15 +628,6 @@ public class EpdgTunnelManagerTest { setupMockForGetConfig(bundle); - when(mMockEpdgSelector.getValidatedServerList( - anyInt(), - anyInt(), - eq(false), - eq(false), - eq(mMockNetwork), - any(EpdgSelector.EpdgSelectorCallback.class))) - .thenReturn(new IwlanError(IwlanError.NO_ERROR)); - doReturn(null) .when(mMockIkeSessionCreator) .createIkeSession( @@ -549,7 +642,8 @@ public class EpdgTunnelManagerTest { boolean ret = mEpdgTunnelManager.bringUpTunnel( getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP), - mMockIwlanTunnelCallback); + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics); assertTrue(ret); mTestLooper.dispatchAll(); @@ -592,15 +686,6 @@ public class EpdgTunnelManagerTest { setupMockForGetConfig(bundle); - when(mMockEpdgSelector.getValidatedServerList( - anyInt(), - anyInt(), - eq(false), - eq(false), - eq(mMockNetwork), - any(EpdgSelector.EpdgSelectorCallback.class))) - .thenReturn(new IwlanError(IwlanError.NO_ERROR)); - doReturn(null) .when(mMockIkeSessionCreator) .createIkeSession( @@ -615,7 +700,8 @@ public class EpdgTunnelManagerTest { boolean ret = mEpdgTunnelManager.bringUpTunnel( getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP), - mMockIwlanTunnelCallback); + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics); assertTrue(ret); mTestLooper.dispatchAll(); @@ -649,14 +735,6 @@ public class EpdgTunnelManagerTest { bundle.putInt(CarrierConfigManager.Iwlan.KEY_DPD_TIMER_SEC_INT, testDpdDelay); setupMockForGetConfig(bundle); - when(mMockEpdgSelector.getValidatedServerList( - anyInt(), - anyInt(), - eq(false), - eq(false), - eq(mMockNetwork), - any(EpdgSelector.EpdgSelectorCallback.class))) - .thenReturn(new IwlanError(IwlanError.NO_ERROR)); doReturn(null) .when(mMockIkeSessionCreator) @@ -672,7 +750,8 @@ public class EpdgTunnelManagerTest { boolean ret = mEpdgTunnelManager.bringUpTunnel( getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP), - mMockIwlanTunnelCallback); + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics); assertTrue(ret); mTestLooper.dispatchAll(); @@ -707,14 +786,6 @@ public class EpdgTunnelManagerTest { doReturn(0L).when(mEpdgTunnelManager).reportIwlanError(eq(testApnName), eq(error)); setupMockForGetConfig(null); - when(mMockEpdgSelector.getValidatedServerList( - anyInt(), - anyInt(), - eq(false), - eq(false), - eq(mMockNetwork), - any(EpdgSelector.EpdgSelectorCallback.class))) - .thenReturn(new IwlanError(IwlanError.NO_ERROR)); doReturn(null) .doReturn(null) @@ -731,7 +802,8 @@ public class EpdgTunnelManagerTest { boolean ret = mEpdgTunnelManager.bringUpTunnel( getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP), - mMockIwlanTunnelCallback); + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics); assertTrue(ret); mTestLooper.dispatchAll(); @@ -743,7 +815,7 @@ public class EpdgTunnelManagerTest { EpdgTunnelManager.TmIkeSessionCallback ikeSessionCallback = verifyCreateIkeSession(ipList2.get(0)); - ikeSessionCallback.onClosedExceptionally( + ikeSessionCallback.onClosedWithException( new IkeInternalException(new IOException("Retransmitting failure"))); mTestLooper.dispatchAll(); @@ -764,14 +836,6 @@ public class EpdgTunnelManagerTest { doReturn(0L).when(mEpdgTunnelManager).reportIwlanError(eq(testApnName), eq(error)); setupMockForGetConfig(null); - when(mMockEpdgSelector.getValidatedServerList( - anyInt(), - anyInt(), - eq(false), - eq(false), - eq(mMockNetwork), - any(EpdgSelector.EpdgSelectorCallback.class))) - .thenReturn(new IwlanError(IwlanError.NO_ERROR)); doReturn(null) .doReturn(null) @@ -788,7 +852,8 @@ public class EpdgTunnelManagerTest { boolean ret = mEpdgTunnelManager.bringUpTunnel( getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP), - mMockIwlanTunnelCallback); + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics); assertTrue(ret); mTestLooper.dispatchAll(); @@ -801,7 +866,7 @@ public class EpdgTunnelManagerTest { EpdgTunnelManager.TmIkeSessionCallback ikeSessionCallback = verifyCreateIkeSession(ipList2.get(1)); - ikeSessionCallback.onClosedExceptionally( + ikeSessionCallback.onClosedWithException( new IkeInternalException(new IOException("Retransmitting failure"))); mTestLooper.dispatchAll(); @@ -834,7 +899,7 @@ public class EpdgTunnelManagerTest { LinkAddress l1 = new LinkAddress(a1, 64); InetAddress src = InetAddress.getByName("2600:381:4872:5d1e:0:10:3582:a501"); EpdgTunnelManager.TunnelConfig tf = - mEpdgTunnelManager.new TunnelConfig(null, null, src, 64); + mEpdgTunnelManager.new TunnelConfig(null, null, null, src, 64); assertTrue(tf.isPrefixSameAsSrcIP(l1)); // different prefix length @@ -888,14 +953,7 @@ public class EpdgTunnelManagerTest { } setupMockForGetConfig(null); - when(mMockEpdgSelector.getValidatedServerList( - anyInt(), - anyInt(), - eq(false), - eq(false), - eq(mMockNetwork), - any(EpdgSelector.EpdgSelectorCallback.class))) - .thenReturn(new IwlanError(IwlanError.NO_ERROR)); + doReturn(null) .doReturn(null) .when(mMockIkeSessionCreator) @@ -911,7 +969,8 @@ public class EpdgTunnelManagerTest { boolean ret = mEpdgTunnelManager.bringUpTunnel( getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP), - mMockIwlanTunnelCallback); + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics); assertTrue(ret); mTestLooper.dispatchAll(); @@ -941,7 +1000,7 @@ public class EpdgTunnelManagerTest { ike3gppCallback.onIke3gppDataReceived(ike3gppInfoList); EpdgTunnelManager.TmIkeSessionCallback ikeSessionCallback = ikeSessionCallbackCaptor.getValue(); - ikeSessionCallback.onClosedExceptionally(new IkeInternalException(new Exception())); + ikeSessionCallback.onClosedWithException(new IkeInternalException(new Exception())); mTestLooper.dispatchAll(); // if expected backoff time is negative - verify that backoff time is not reported. @@ -964,7 +1023,6 @@ public class EpdgTunnelManagerTest { TunnelSetupRequest ret = TunnelSetupRequest.builder() .setApnName(apnName) - .setNetwork(mMockNetwork) .setIsRoaming(false /*isRoaming*/) .setIsEmergency(false /*IsEmergency*/) .setRequestPcscf(false /*requestPcscf*/) @@ -977,7 +1035,6 @@ public class EpdgTunnelManagerTest { private TunnelSetupRequest getHandoverTunnelSetupRequest(String apnName, int apnIpProtocol) { TunnelSetupRequest.Builder bld = TunnelSetupRequest.builder(); bld.setApnName(apnName) - .setNetwork(mMockNetwork) .setIsRoaming(false /*isRoaming*/) .setIsEmergency(false /*IsEmergency*/) .setRequestPcscf(false /*requestPcscf*/) @@ -1014,6 +1071,12 @@ public class EpdgTunnelManagerTest { SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_384_192, SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_512_256, }); + if (!bundle.containsKey( + CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_IP_TYPE_PREFERENCE_INT)) { + bundle.putInt( + CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_IP_TYPE_PREFERENCE_INT, + CarrierConfigManager.Iwlan.EPDG_ADDRESS_IPV4_PREFERRED); + } when(mMockContext.getSystemService(eq(CarrierConfigManager.class))) .thenReturn(mMockCarrierConfigManager); when(mMockContext.getSystemService(eq(ConnectivityManager.class))) @@ -1036,28 +1099,36 @@ public class EpdgTunnelManagerTest { } @Test - public void testHandleOnClosedWithEpdgAddressSelected_True() throws Exception { + public void testHandleOnClosedWithEpdgConnected_True() throws Exception { String testApnName = "www.xyz.com"; - IwlanError error = new IwlanError(IwlanError.NETWORK_FAILURE); + IwlanError error = + new IwlanError(IwlanError.IKE_SESSION_CLOSED_BEFORE_CHILD_SESSION_OPENED); doReturn(0L).when(mEpdgTunnelManager).reportIwlanError(eq(testApnName), eq(error)); mEpdgTunnelManager.putApnNameToTunnelConfig( - testApnName, mMockIkeSession, mMockIwlanTunnelCallback, null, 0); - - mEpdgTunnelManager.setIsEpdgAddressSelected(true); - - mEpdgTunnelManager.getTmIkeSessionCallback(testApnName).onClosed(); + testApnName, + mMockIkeSession, + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics, + null, + 0); + int token = mEpdgTunnelManager.incrementAndGetCurrentTokenForApn(testApnName); + + mEpdgTunnelManager.onConnectedToEpdg(true); + mEpdgTunnelManager.setEpdgAddress(InetAddresses.parseNumericAddress(EPDG_ADDRESS)); + + mEpdgTunnelManager.getTmIkeSessionCallback(testApnName, token).onClosed(); mTestLooper.dispatchAll(); verify(mMockIwlanTunnelCallback, times(1)).onClosed(eq(testApnName), eq(error)); - verify(mEpdgTunnelManager, times(2)).resetTunnelManagerState(); verify(mEpdgTunnelManager, times(1)).reportIwlanError(eq(testApnName), eq(error)); } @Test - public void testHandleOnClosedWithEpdgAddressSelected_False() throws Exception { + public void testHandleOnClosedWithEpdgConnected_False() throws Exception { String testApnName = "www.xyz.com"; - IwlanError error = new IwlanError(IwlanError.NETWORK_FAILURE); + IwlanError error = + new IwlanError(IwlanError.IKE_SESSION_CLOSED_BEFORE_CHILD_SESSION_OPENED); doReturn(0L).when(mEpdgTunnelManager).reportIwlanError(eq(testApnName), eq(error)); @@ -1068,7 +1139,8 @@ public class EpdgTunnelManagerTest { boolean ret = mEpdgTunnelManager.bringUpTunnel( getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP), - mMockIwlanTunnelCallback); + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics); assertTrue(ret); mTestLooper.dispatchAll(); @@ -1076,27 +1148,35 @@ public class EpdgTunnelManagerTest { EXPECTED_EPDG_ADDRESSES, new IwlanError(IwlanError.NO_ERROR), 1); mTestLooper.dispatchAll(); - mEpdgTunnelManager.setIsEpdgAddressSelected(false); + mEpdgTunnelManager.onConnectedToEpdg(false); - mEpdgTunnelManager.getTmIkeSessionCallback(testApnName).onClosed(); + mEpdgTunnelManager.getTmIkeSessionCallback(testApnName, DEFAULT_TOKEN).onClosed(); mTestLooper.dispatchAll(); verify(mMockIwlanTunnelCallback, times(1)).onClosed(eq(testApnName), eq(error)); - verify(mEpdgTunnelManager, times(2)).resetTunnelManagerState(); verify(mEpdgTunnelManager, times(1)).reportIwlanError(eq(testApnName), eq(error)); } private void setOneTunnelOpened(String apnName) throws Exception { mEpdgTunnelManager.putApnNameToTunnelConfig( - apnName, mMockIkeSession, mMockIwlanTunnelCallback, null, 0); - setVariable(mEpdgTunnelManager, "mLocalAddresses", EXPECTED_LOCAL_ADDRESSES); + apnName, + mMockIkeSession, + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics, + null, + 0); mEpdgTunnelManager.validateAndSetEpdgAddress(EXPECTED_EPDG_ADDRESSES); - mEpdgTunnelManager.setIsEpdgAddressSelected(true); + mEpdgTunnelManager.onConnectedToEpdg(true); + } + + private IkeSessionArgumentCaptors verifyBringUpTunnelWithDnsQuery(String apnName) { + return verifyBringUpTunnelWithDnsQuery(apnName, null); } - private ChildSessionCallback verifyBringUpTunnelWithDnsQuery( + private IkeSessionArgumentCaptors verifyBringUpTunnelWithDnsQuery( String apnName, IkeSession ikeSession) { reset(mMockIwlanTunnelCallback); + IkeSessionArgumentCaptors ikeSessionArgumentCaptors = new IkeSessionArgumentCaptors(); verifyBringUpTunnel(apnName, true /* needPendingBringUpReq */); @@ -1104,11 +1184,11 @@ public class EpdgTunnelManagerTest { .when(mMockIkeSessionCreator) .createIkeSession( eq(mMockContext), - any(IkeSessionParams.class), - any(ChildSessionParams.class), + ikeSessionArgumentCaptors.mIkeSessionParamsCaptor.capture(), + ikeSessionArgumentCaptors.mChildSessionParamsCaptor.capture(), any(Executor.class), - any(IkeSessionCallback.class), - mChildSessionCallbackCaptor.capture()); + ikeSessionArgumentCaptors.mIkeSessionCallbackCaptor.capture(), + ikeSessionArgumentCaptors.mChildSessionCallbackCaptor.capture()); mEpdgTunnelManager.sendSelectionRequestComplete( EXPECTED_EPDG_ADDRESSES, new IwlanError(IwlanError.NO_ERROR), 1); @@ -1117,56 +1197,62 @@ public class EpdgTunnelManagerTest { verify(mMockIkeSessionCreator, times(1)) .createIkeSession( eq(mMockContext), - any(IkeSessionParams.class), - any(ChildSessionParams.class), + ikeSessionArgumentCaptors.mIkeSessionParamsCaptor.capture(), + ikeSessionArgumentCaptors.mChildSessionParamsCaptor.capture(), any(Executor.class), - any(IkeSessionCallback.class), - mChildSessionCallbackCaptor.capture()); + ikeSessionArgumentCaptors.mIkeSessionCallbackCaptor.capture(), + ikeSessionArgumentCaptors.mChildSessionCallbackCaptor.capture()); - return mChildSessionCallbackCaptor.getValue(); + return ikeSessionArgumentCaptors; } - private ChildSessionCallback verifyBringUpTunnel( + private IkeSessionArgumentCaptors verifyBringUpTunnel( String apnName, boolean needPendingBringUpReq) { reset(mMockIkeSessionCreator); + IkeSessionArgumentCaptors ikeSessionArgumentCaptors = new IkeSessionArgumentCaptors(); doReturn(null) .when(mMockIkeSessionCreator) .createIkeSession( eq(mMockContext), - any(IkeSessionParams.class), - any(ChildSessionParams.class), + ikeSessionArgumentCaptors.mIkeSessionParamsCaptor.capture(), + ikeSessionArgumentCaptors.mChildSessionParamsCaptor.capture(), any(Executor.class), - any(IkeSessionCallback.class), - mChildSessionCallbackCaptor.capture()); + ikeSessionArgumentCaptors.mIkeSessionCallbackCaptor.capture(), + ikeSessionArgumentCaptors.mChildSessionCallbackCaptor.capture()); doReturn(true).when(mEpdgTunnelManager).canBringUpTunnel(eq(apnName)); boolean ret = mEpdgTunnelManager.bringUpTunnel( getBasicTunnelSetupRequest(apnName, ApnSetting.PROTOCOL_IP), - mMockIwlanTunnelCallback); + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics); assertTrue(ret); mTestLooper.dispatchAll(); verify(mMockIkeSessionCreator, times(needPendingBringUpReq ? 0 : 1)) .createIkeSession( eq(mMockContext), - any(IkeSessionParams.class), - any(ChildSessionParams.class), + ikeSessionArgumentCaptors.mIkeSessionParamsCaptor.capture(), + ikeSessionArgumentCaptors.mChildSessionParamsCaptor.capture(), any(Executor.class), - any(IkeSessionCallback.class), - mChildSessionCallbackCaptor.capture()); + ikeSessionArgumentCaptors.mIkeSessionCallbackCaptor.capture(), + ikeSessionArgumentCaptors.mChildSessionCallbackCaptor.capture()); - return needPendingBringUpReq ? null : mChildSessionCallbackCaptor.getValue(); + return ikeSessionArgumentCaptors; } - private void verifyTunnelOnOpened(String apnName) { - verifyTunnelOnOpened(apnName, mChildSessionCallbackCaptor.getValue()); - } + private void verifyTunnelOnOpened(String apnName, ChildSessionCallback childSessionCallback) + throws Exception { + clearInvocations(mMockIpSecManager); + doReturn(0L) + .when(mEpdgTunnelManager) + .reportIwlanError(eq(apnName), eq(new IwlanError(IwlanError.NO_ERROR))); - private void verifyTunnelOnOpened(String apnName, ChildSessionCallback childSessionCallback) { - mEpdgTunnelManager.getTmIkeSessionCallback(apnName).onOpened(mMockIkeSessionConfiguration); + mEpdgTunnelManager + .getTmIkeSessionCallback(apnName, mEpdgTunnelManager.getCurrentTokenForApn(apnName)) + .onOpened(mMockIkeSessionConfiguration); mTestLooper.dispatchAll(); childSessionCallback.onIpSecTransformCreated( mMockedIpSecTransformIn, IpSecManager.DIRECTION_IN); @@ -1174,21 +1260,35 @@ public class EpdgTunnelManagerTest { childSessionCallback.onIpSecTransformCreated( mMockedIpSecTransformOut, IpSecManager.DIRECTION_OUT); mTestLooper.dispatchAll(); + verify(mMockIpSecManager, times(1)) + .createIpSecTunnelInterface( + any(InetAddress.class), any(InetAddress.class), eq(mMockDefaultNetwork)); + childSessionCallback.onOpened(mMockChildSessionConfiguration); mTestLooper.dispatchAll(); - + verify(mEpdgTunnelManager, times(1)) + .reportIwlanError(eq(apnName), eq(new IwlanError(IwlanError.NO_ERROR))); verify(mMockIwlanTunnelCallback, times(1)).onOpened(eq(apnName), any()); } @Test - public void testHandleOnOpenedWithEpdgAddressSelected_True() throws Exception { + public void testHandleOnOpenedWithEpdgConnected_True() throws Exception { final String openedApnName = "ims"; final String toBeOpenedApnName = "mms"; setOneTunnelOpened(openedApnName); - verifyBringUpTunnel(toBeOpenedApnName, false /* needPendingBringUpReq */); - verifyTunnelOnOpened(toBeOpenedApnName); + // FIXME: Since the network from bringUpTunnel() will only be stored for the first request, + // and we are skipping the first tunnel setup procedure in this test case, it is necessary + // to set the network instance directly. + mEpdgTunnelManager.updateNetwork(mMockDefaultNetwork, mMockLinkProperties); + mTestLooper.dispatchAll(); + + IkeSessionArgumentCaptors ikeSessionArgumentCaptors = + verifyBringUpTunnel(toBeOpenedApnName, false /* needPendingBringUpReq */); + ChildSessionCallback childSessionCallback = + ikeSessionArgumentCaptors.mChildSessionCallbackCaptor.getValue(); + verifyTunnelOnOpened(toBeOpenedApnName, childSessionCallback); } @Test @@ -1196,36 +1296,50 @@ public class EpdgTunnelManagerTest { final String firstApnName = "ims"; final String secondApnName = "mms"; - ChildSessionCallback firstCallback = verifyBringUpTunnelWithDnsQuery(firstApnName, null); - verifyBringUpTunnel(secondApnName, true /* needPendingBringUpReq */); + IkeSessionArgumentCaptors firstTunnelArgumentCaptors = + verifyBringUpTunnelWithDnsQuery(firstApnName); + ChildSessionCallback firstCallback = + firstTunnelArgumentCaptors.mChildSessionCallbackCaptor.getValue(); + + IkeSessionArgumentCaptors secondTunnelArgumentCaptors = + verifyBringUpTunnel(secondApnName, true /* needPendingBringUpReq */); verifyTunnelOnOpened(firstApnName, firstCallback); - verifyTunnelOnOpened(secondApnName, mChildSessionCallbackCaptor.getValue()); + + ChildSessionCallback secondCallback = + secondTunnelArgumentCaptors.mChildSessionCallbackCaptor.getValue(); + verifyTunnelOnOpened(secondApnName, secondCallback); } @Test - public void testHandleOnClosedExceptionallyWithEpdgAddressSelected_True() throws Exception { + public void testHandleOnClosedExceptionallyWithEpdgConnected_True() throws Exception { String testApnName = "www.xyz.com"; IwlanError error = new IwlanError(mMockIkeException); doReturn(0L).when(mEpdgTunnelManager).reportIwlanError(eq(testApnName), eq(error)); mEpdgTunnelManager.putApnNameToTunnelConfig( - testApnName, mMockIkeSession, mMockIwlanTunnelCallback, null, 0); + testApnName, + mMockIkeSession, + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics, + null, + 0); + int token = mEpdgTunnelManager.incrementAndGetCurrentTokenForApn(testApnName); - mEpdgTunnelManager.setIsEpdgAddressSelected(true); + mEpdgTunnelManager.onConnectedToEpdg(true); + mEpdgTunnelManager.setEpdgAddress(InetAddresses.parseNumericAddress(EPDG_ADDRESS)); mEpdgTunnelManager - .getTmIkeSessionCallback(testApnName) - .onClosedExceptionally(mMockIkeException); + .getTmIkeSessionCallback(testApnName, token) + .onClosedWithException(mMockIkeException); mTestLooper.dispatchAll(); verify(mMockIwlanTunnelCallback, times(1)).onClosed(eq(testApnName), eq(error)); - verify(mEpdgTunnelManager, times(2)).resetTunnelManagerState(); verify(mEpdgTunnelManager, times(1)).reportIwlanError(eq(testApnName), eq(error)); } @Test - public void testHandleOnClosedExceptionallyWithEpdgAddressSelected_False() throws Exception { + public void testHandleOnClosedExceptionallyWithEpdgConnected_False() throws Exception { String testApnName = "www.xyz.com"; IwlanError error = new IwlanError(mMockIkeException); @@ -1238,7 +1352,8 @@ public class EpdgTunnelManagerTest { boolean ret = mEpdgTunnelManager.bringUpTunnel( getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP), - mMockIwlanTunnelCallback); + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics); assertTrue(ret); mTestLooper.dispatchAll(); @@ -1246,15 +1361,14 @@ public class EpdgTunnelManagerTest { EXPECTED_EPDG_ADDRESSES, new IwlanError(IwlanError.NO_ERROR), 1); mTestLooper.dispatchAll(); - mEpdgTunnelManager.setIsEpdgAddressSelected(false); + mEpdgTunnelManager.onConnectedToEpdg(false); mEpdgTunnelManager - .getTmIkeSessionCallback(testApnName) - .onClosedExceptionally(mMockIkeException); + .getTmIkeSessionCallback(testApnName, DEFAULT_TOKEN) + .onClosedWithException(mMockIkeException); mTestLooper.dispatchAll(); verify(mMockIwlanTunnelCallback, times(1)).onClosed(eq(testApnName), any(IwlanError.class)); - verify(mEpdgTunnelManager, times(2)).resetTunnelManagerState(); verify(mEpdgTunnelManager, times(1)).reportIwlanError(eq(testApnName), eq(error)); } @@ -1265,12 +1379,18 @@ public class EpdgTunnelManagerTest { doReturn(0L).when(mEpdgTunnelManager).reportIwlanError(eq(testApnName), eq(error)); mEpdgTunnelManager.putApnNameToTunnelConfig( - testApnName, mMockIkeSession, mMockIwlanTunnelCallback, null, 0); + testApnName, + mMockIkeSession, + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics, + null, + 0); + int token = mEpdgTunnelManager.incrementAndGetCurrentTokenForApn(testApnName); when(mMockIkeSessionConfiguration.getPcscfServers()).thenReturn(EXPECTED_EPDG_ADDRESSES); mEpdgTunnelManager - .getTmIkeSessionCallback(testApnName) + .getTmIkeSessionCallback(testApnName, token) .onOpened(mMockIkeSessionConfiguration); mTestLooper.dispatchAll(); @@ -1286,8 +1406,10 @@ public class EpdgTunnelManagerTest { doThrow(new IllegalArgumentException()) .when(mMockIpSecManager) .applyTunnelModeTransform(eq(mMockIpSecTunnelInterface), anyInt(), any()); - ChildSessionCallback childSessionCallback = + IkeSessionArgumentCaptors ikeSessionArgumentCaptors = verifyBringUpTunnelWithDnsQuery(testApnName, mMockIkeSession); + ChildSessionCallback childSessionCallback = + ikeSessionArgumentCaptors.mChildSessionCallbackCaptor.getValue(); childSessionCallback.onIpSecTransformCreated( mMockedIpSecTransformIn, IpSecManager.DIRECTION_IN); mTestLooper.dispatchAll(); @@ -1300,17 +1422,19 @@ public class EpdgTunnelManagerTest { String testApnName = "ims"; when(mMockConnectivityManager.getLinkProperties(any())).thenReturn(mMockLinkProperties); - ChildSessionCallback childSessionCallback = + IkeSessionArgumentCaptors ikeSessionArgumentCaptors = verifyBringUpTunnelWithDnsQuery(testApnName, mMockIkeSession); + ChildSessionCallback childSessionCallback = + ikeSessionArgumentCaptors.mChildSessionCallbackCaptor.getValue(); childSessionCallback.onIpSecTransformCreated( mMockedIpSecTransformIn, IpSecManager.DIRECTION_IN); mEpdgTunnelManager - .getTmIkeSessionCallback(testApnName) + .getTmIkeSessionCallback(testApnName, DEFAULT_TOKEN) .onIkeSessionConnectionInfoChanged(mMockIkeSessionConnectionInfo); mTestLooper.dispatchAll(); - verify(mMockIpSecTunnelInterface, times(1)).setUnderlyingNetwork(mMockNetwork); + verify(mMockIpSecTunnelInterface, times(1)).setUnderlyingNetwork(mMockDefaultNetwork); } @Test @@ -1319,13 +1443,15 @@ public class EpdgTunnelManagerTest { String testApnName = "ims"; when(mMockConnectivityManager.getLinkProperties(any())).thenReturn(null); - ChildSessionCallback childSessionCallback = + IkeSessionArgumentCaptors ikeSessionArgumentCaptors = verifyBringUpTunnelWithDnsQuery(testApnName, mMockIkeSession); + ChildSessionCallback childSessionCallback = + ikeSessionArgumentCaptors.mChildSessionCallbackCaptor.getValue(); childSessionCallback.onIpSecTransformCreated( mMockedIpSecTransformIn, IpSecManager.DIRECTION_IN); mEpdgTunnelManager - .getTmIkeSessionCallback(testApnName) + .getTmIkeSessionCallback(testApnName, DEFAULT_TOKEN) .onIkeSessionConnectionInfoChanged(mMockIkeSessionConnectionInfo); mTestLooper.dispatchAll(); @@ -1368,15 +1494,6 @@ public class EpdgTunnelManagerTest { PersistableBundle bundle = new PersistableBundle(); setupMockForGetConfig(bundle); - when(mMockEpdgSelector.getValidatedServerList( - anyInt(), - anyInt(), - eq(false), - eq(false), - eq(mMockNetwork), - any(EpdgSelector.EpdgSelectorCallback.class))) - .thenReturn(new IwlanError(IwlanError.NO_ERROR)); - doReturn(null) .when(mMockIkeSessionCreator) .createIkeSession( @@ -1394,12 +1511,14 @@ public class EpdgTunnelManagerTest { ret = mEpdgTunnelManager.bringUpTunnel( getHandoverTunnelSetupRequest(TEST_APN_NAME, apnProtocol), - mMockIwlanTunnelCallback); + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics); } else { ret = mEpdgTunnelManager.bringUpTunnel( getBasicTunnelSetupRequest(TEST_APN_NAME, apnProtocol), - mMockIwlanTunnelCallback); + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics); } assertTrue(ret); @@ -1499,17 +1618,23 @@ public class EpdgTunnelManagerTest { doReturn(0L).when(mEpdgTunnelManager).reportIwlanError(eq(testApnName), eq(error)); mEpdgTunnelManager.putApnNameToTunnelConfig( - testApnName, mMockIkeSession, mMockIwlanTunnelCallback, null, 0); + testApnName, + mMockIkeSession, + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics, + null, + 0); + int token = mEpdgTunnelManager.incrementAndGetCurrentTokenForApn(testApnName); - mEpdgTunnelManager.setIsEpdgAddressSelected(true); + mEpdgTunnelManager.onConnectedToEpdg(true); + mEpdgTunnelManager.setEpdgAddress(InetAddresses.parseNumericAddress(EPDG_ADDRESS)); mEpdgTunnelManager - .getTmIkeSessionCallback(testApnName) - .onClosedExceptionally(mockException); + .getTmIkeSessionCallback(testApnName, token) + .onClosedWithException(mockException); mTestLooper.dispatchAll(); verify(mMockIwlanTunnelCallback, times(1)).onClosed(eq(testApnName), eq(error)); - verify(mEpdgTunnelManager, times(2)).resetTunnelManagerState(); verify(mEpdgTunnelManager, times(1)).reportIwlanError(eq(testApnName), eq(error)); } @@ -1527,19 +1652,43 @@ public class EpdgTunnelManagerTest { boolean ret = mEpdgTunnelManager.bringUpTunnel( getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP), - mMockIwlanTunnelCallback); + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics); assertTrue(ret); mTestLooper.dispatchAll(); mEpdgTunnelManager.sendSelectionRequestComplete(null, error, 1); mTestLooper.dispatchAll(); - mEpdgTunnelManager.setIsEpdgAddressSelected(false); + mEpdgTunnelManager.onConnectedToEpdg(false); verify(mEpdgTunnelManager, times(1)).reportIwlanError(eq(testApnName), eq(error)); } @Test + public void testNeverReportIwlanErrorWhenCloseAnOpenedTunnel() throws Exception { + IkeInternalException ikeException = + new IkeInternalException(new IOException("Retransmitting failure")); + + IkeSessionArgumentCaptors ikeSessionArgumentCaptors = + verifyBringUpTunnelWithDnsQuery(TEST_APN_NAME); + + ChildSessionCallback childSessionCallback = + ikeSessionArgumentCaptors.mChildSessionCallbackCaptor.getValue(); + verifyTunnelOnOpened(TEST_APN_NAME, childSessionCallback); + + reset(mEpdgTunnelManager); // reset number of times of reportIwlanError() + + mEpdgTunnelManager + .getTmIkeSessionCallback(TEST_APN_NAME, 0) + .onClosedWithException(ikeException); + mTestLooper.dispatchAll(); + verify(mEpdgTunnelManager, never()).reportIwlanError(eq(TEST_APN_NAME), any()); + verify(mMockIwlanTunnelCallback, times(1)) + .onClosed(eq(TEST_APN_NAME), eq(new IwlanError(ikeException))); + } + + @Test public void testCanBringUpTunnel() throws Exception { String testApnName = "www.xyz.com"; IwlanError error = new IwlanError(mMockIkeException); @@ -1553,7 +1702,8 @@ public class EpdgTunnelManagerTest { boolean ret = mEpdgTunnelManager.bringUpTunnel( getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP), - mMockIwlanTunnelCallback); + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics); assertTrue(ret); mTestLooper.dispatchAll(); verify(mMockIwlanTunnelCallback, times(1)).onClosed(eq(testApnName), eq(error)); @@ -1567,15 +1717,6 @@ public class EpdgTunnelManagerTest { PersistableBundle bundle = new PersistableBundle(); setupMockForGetConfig(bundle); - when(mMockEpdgSelector.getValidatedServerList( - anyInt(), - anyInt(), - eq(false), - eq(false), - eq(mMockNetwork), - any(EpdgSelector.EpdgSelectorCallback.class))) - .thenReturn(new IwlanError(IwlanError.NO_ERROR)); - doReturn(null) .when(mMockIkeSessionCreator) .createIkeSession( @@ -1593,7 +1734,8 @@ public class EpdgTunnelManagerTest { mEpdgTunnelManager.bringUpTunnel( getBasicTunnelSetupRequest( TEST_APN_NAME, ApnSetting.PROTOCOL_IPV6, PDU_SESSION_ID), - mMockIwlanTunnelCallback); + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics); assertTrue(ret); mTestLooper.dispatchAll(); @@ -1637,14 +1779,6 @@ public class EpdgTunnelManagerTest { bundle.putInt(CarrierConfigManager.Iwlan.KEY_NATT_KEEP_ALIVE_TIMER_SEC_INT, nattTimer); setupMockForGetConfig(bundle); - when(mMockEpdgSelector.getValidatedServerList( - anyInt(), - anyInt(), - eq(false), - eq(false), - eq(mMockNetwork), - any(EpdgSelector.EpdgSelectorCallback.class))) - .thenReturn(new IwlanError(IwlanError.NO_ERROR)); doReturn(null) .when(mMockIkeSessionCreator) @@ -1660,7 +1794,8 @@ public class EpdgTunnelManagerTest { boolean ret = mEpdgTunnelManager.bringUpTunnel( getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP), - mMockIwlanTunnelCallback); + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics); assertTrue(ret); mTestLooper.dispatchAll(); @@ -1698,7 +1833,6 @@ public class EpdgTunnelManagerTest { TunnelSetupRequest tsr = TunnelSetupRequest.builder() .setApnName(testApnName) - .setNetwork(mMockNetwork) .setApnIpProtocol(ApnSetting.PROTOCOL_IPV4V6) .setSrcIpv6Address(testAddressV6) .setSrcIpv6AddressPrefixLength(ipv6AddressLen) @@ -1710,14 +1844,6 @@ public class EpdgTunnelManagerTest { .build(); setupMockForGetConfig(null); - when(mMockEpdgSelector.getValidatedServerList( - anyInt(), - anyInt(), - eq(isRoaming), - eq(isEmergency), - eq(mMockNetwork), - any(EpdgSelector.EpdgSelectorCallback.class))) - .thenReturn(new IwlanError(IwlanError.NO_ERROR)); doReturn(null) .when(mMockIkeSessionCreator) @@ -1730,7 +1856,9 @@ public class EpdgTunnelManagerTest { any(ChildSessionCallback.class)); doReturn(true).when(mEpdgTunnelManager).canBringUpTunnel(eq(testApnName)); - boolean ret = mEpdgTunnelManager.bringUpTunnel(tsr, mMockIwlanTunnelCallback); + boolean ret = + mEpdgTunnelManager.bringUpTunnel( + tsr, mMockIwlanTunnelCallback, mMockIwlanTunnelMetrics); assertTrue(ret); mTestLooper.dispatchAll(); @@ -1739,9 +1867,10 @@ public class EpdgTunnelManagerTest { .getValidatedServerList( anyInt(), anyInt(), // only Ipv6 address is added + anyInt(), eq(isRoaming), eq(isEmergency), - eq(mMockNetwork), + eq(mMockDefaultNetwork), any(EpdgSelector.EpdgSelectorCallback.class)); mEpdgTunnelManager.sendSelectionRequestComplete( @@ -1771,7 +1900,7 @@ public class EpdgTunnelManagerTest { assertEquals(ikeId.fqdn, testApnName); // verify Network - assertEquals(ikeSessionParams.getNetwork(), mMockNetwork); + assertEquals(ikeSessionParams.getNetwork(), mMockDefaultNetwork); // verify requestPcscf (true) with Apn protocol IPV6 // it should add the pcscf config requests of type ConfigRequestIpv6PcscfServer and @@ -1835,7 +1964,8 @@ public class EpdgTunnelManagerTest { boolean ret = mEpdgTunnelManager.bringUpTunnel( getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP), - mMockIwlanTunnelCallback); + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics); assertTrue(ret); mTestLooper.dispatchAll(); @@ -1859,12 +1989,14 @@ public class EpdgTunnelManagerTest { when(mMockSubscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(DEFAULT_SLOT_INDEX)) .thenReturn(mMockSubscriptionInfo) + .thenReturn(mMockSubscriptionInfo) .thenReturn(null); boolean ret = mEpdgTunnelManager.bringUpTunnel( getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP), - mMockIwlanTunnelCallback); + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics); assertTrue(ret); mTestLooper.dispatchAll(); @@ -1874,4 +2006,474 @@ public class EpdgTunnelManagerTest { verify(mMockIwlanTunnelCallback, times(1)).onClosed(eq(testApnName), eq(error)); } + + @Test + public void testCloseTunnelWithEpdgSelectionIncomplete() { + // Bring up tunnel + doReturn(true).when(mEpdgTunnelManager).canBringUpTunnel(eq(TEST_APN_NAME)); + + boolean ret = + mEpdgTunnelManager.bringUpTunnel( + getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP), + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics); + assertTrue(ret); + + // close tunnel when ePDG selection is incomplete + mEpdgTunnelManager.closeTunnel( + TEST_APN_NAME, + false /*forceClose*/, + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics); + mTestLooper.dispatchAll(); + + verify(mMockIwlanTunnelCallback, times(1)) + .onClosed(eq(TEST_APN_NAME), eq(new IwlanError(IwlanError.NO_ERROR))); + ArgumentCaptor<OnClosedMetrics> metricsCaptor = + ArgumentCaptor.forClass(OnClosedMetrics.class); + verify(mMockIwlanTunnelMetrics, times(1)).onClosed(metricsCaptor.capture()); + assertEquals(TEST_APN_NAME, metricsCaptor.getValue().getApnName()); + assertNull(metricsCaptor.getValue().getEpdgServerAddress()); + } + + @Test + public void testIgnoreSignalFromObsoleteCallback() throws Exception { + int transactionId = 0; + + // testApnName with token 0 + setupTunnelBringup(TEST_APN_NAME, EXPECTED_EPDG_ADDRESSES, ++transactionId); + mEpdgTunnelManager.onConnectedToEpdg(true); + + IwlanError error = new IwlanError(mMockIkeException); + doReturn(0L).when(mEpdgTunnelManager).reportIwlanError(eq(TEST_APN_NAME), eq(error)); + + mEpdgTunnelManager + .getTmIkeSessionCallback(TEST_APN_NAME, 0 /* token */) + .onClosedWithException(mMockIkeException); + mTestLooper.dispatchAll(); + verify(mMockIwlanTunnelCallback, times(1)).onClosed(eq(TEST_APN_NAME), eq(error)); + assertNull(mEpdgTunnelManager.getTunnelConfigForApn(TEST_APN_NAME)); + + // testApnName1 with token 1 + setupTunnelBringup(TEST_APN_NAME, EXPECTED_EPDG_ADDRESSES, ++transactionId); + mEpdgTunnelManager.onConnectedToEpdg(true); + + // signal from obsolete callback (token 0), ignore it + reset(mMockIwlanTunnelCallback); + mEpdgTunnelManager + .getTmIkeSessionCallback(TEST_APN_NAME, 0 /* token */) + .onClosedWithException(mMockIkeException); + mTestLooper.dispatchAll(); + verify(mMockIwlanTunnelCallback, never()).onClosed(eq(TEST_APN_NAME), eq(error)); + assertNotNull(mEpdgTunnelManager.getTunnelConfigForApn(TEST_APN_NAME)); + + // signals from active callback + mEpdgTunnelManager + .getTmIkeSessionCallback(TEST_APN_NAME, 1 /* token */) + .onClosedWithException(mMockIkeException); + mTestLooper.dispatchAll(); + verify(mMockIwlanTunnelCallback, times(1)).onClosed(eq(TEST_APN_NAME), eq(error)); + assertNull(mEpdgTunnelManager.getTunnelConfigForApn(TEST_APN_NAME)); + } + + @Test + public void testBringUpTunnelIpv4Preferred() throws Exception { + TunnelSetupRequest TSR = getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP); + doReturn(true).when(mEpdgTunnelManager).canBringUpTunnel(eq(TEST_APN_NAME)); + + PersistableBundle bundle = new PersistableBundle(); + bundle.putInt( + CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_IP_TYPE_PREFERENCE_INT, + CarrierConfigManager.Iwlan.EPDG_ADDRESS_IPV4_PREFERRED); + setupMockForGetConfig(bundle); + + boolean ret = + mEpdgTunnelManager.bringUpTunnel( + TSR, mMockIwlanTunnelCallback, mMockIwlanTunnelMetrics); + assertTrue(ret); + mTestLooper.dispatchAll(); + + verify(mMockEpdgSelector) + .getValidatedServerList( + anyInt(), + eq(EpdgSelector.PROTO_FILTER_IPV4V6), + eq(EpdgSelector.IPV4_PREFERRED), + eq(false), + eq(false), + eq(mMockDefaultNetwork), + any()); + } + + @Test + public void testBringUpTunnelIpv6Preferred() throws Exception { + TunnelSetupRequest TSR = getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP); + doReturn(true).when(mEpdgTunnelManager).canBringUpTunnel(eq(TEST_APN_NAME)); + + PersistableBundle bundle = new PersistableBundle(); + bundle.putInt( + CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_IP_TYPE_PREFERENCE_INT, + CarrierConfigManager.Iwlan.EPDG_ADDRESS_IPV6_PREFERRED); + setupMockForGetConfig(bundle); + + boolean ret = + mEpdgTunnelManager.bringUpTunnel( + TSR, mMockIwlanTunnelCallback, mMockIwlanTunnelMetrics); + assertTrue(ret); + mTestLooper.dispatchAll(); + + verify(mMockEpdgSelector) + .getValidatedServerList( + anyInt(), + eq(EpdgSelector.PROTO_FILTER_IPV4V6), + eq(EpdgSelector.IPV6_PREFERRED), + eq(false), + eq(false), + eq(mMockDefaultNetwork), + any()); + } + + @Test + public void testBringUpTunnelIpv4Only() throws Exception { + TunnelSetupRequest TSR = getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP); + doReturn(true).when(mEpdgTunnelManager).canBringUpTunnel(eq(TEST_APN_NAME)); + + PersistableBundle bundle = new PersistableBundle(); + bundle.putInt( + CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_IP_TYPE_PREFERENCE_INT, + CarrierConfigManager.Iwlan.EPDG_ADDRESS_IPV4_ONLY); + setupMockForGetConfig(bundle); + + boolean ret = + mEpdgTunnelManager.bringUpTunnel( + TSR, mMockIwlanTunnelCallback, mMockIwlanTunnelMetrics); + assertTrue(ret); + mTestLooper.dispatchAll(); + + verify(mMockEpdgSelector) + .getValidatedServerList( + anyInt(), + eq(EpdgSelector.PROTO_FILTER_IPV4), + eq(EpdgSelector.SYSTEM_PREFERRED), + eq(false), + eq(false), + eq(mMockDefaultNetwork), + any()); + } + + @Test + public void testBringUpTunnelIpv6Only() throws Exception { + TunnelSetupRequest TSR = + getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IPV6); + doReturn(true).when(mEpdgTunnelManager).canBringUpTunnel(eq(TEST_APN_NAME)); + doReturn(EXPECTED_IPV6_LOCAL_ADDRESSES) + .when(mEpdgTunnelManager) + .getAddressForNetwork(any(), any()); + + PersistableBundle bundle = new PersistableBundle(); + bundle.putInt( + CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_IP_TYPE_PREFERENCE_INT, + CarrierConfigManager.Iwlan.EPDG_ADDRESS_IPV6_ONLY); + setupMockForGetConfig(bundle); + + boolean ret = + mEpdgTunnelManager.bringUpTunnel( + TSR, mMockIwlanTunnelCallback, mMockIwlanTunnelMetrics); + assertTrue(ret); + mTestLooper.dispatchAll(); + + verify(mMockEpdgSelector) + .getValidatedServerList( + anyInt(), + eq(EpdgSelector.PROTO_FILTER_IPV6), + eq(EpdgSelector.SYSTEM_PREFERRED), + eq(false), + eq(false), + eq(mMockDefaultNetwork), + any()); + } + + @Test + public void testBringUpTunnelIpv6OnlyOnIpv4Wifi() throws Exception { + TunnelSetupRequest TSR = + getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IPV6); + IwlanError error = new IwlanError(IwlanError.EPDG_ADDRESS_ONLY_IPV6_ALLOWED); + doReturn(true).when(mEpdgTunnelManager).canBringUpTunnel(eq(TEST_APN_NAME)); + doReturn(0L).when(mEpdgTunnelManager).reportIwlanError(eq(TEST_APN_NAME), eq(error)); + + PersistableBundle bundle = new PersistableBundle(); + bundle.putInt( + CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_IP_TYPE_PREFERENCE_INT, + CarrierConfigManager.Iwlan.EPDG_ADDRESS_IPV6_ONLY); + setupMockForGetConfig(bundle); + + boolean ret = + mEpdgTunnelManager.bringUpTunnel( + TSR, mMockIwlanTunnelCallback, mMockIwlanTunnelMetrics); + assertTrue(ret); + mTestLooper.dispatchAll(); + + verify(mMockEpdgSelector, never()) + .getValidatedServerList( + anyInt(), + anyInt(), + anyInt(), + eq(false), + eq(false), + eq(mMockDefaultNetwork), + any()); + verify(mEpdgTunnelManager, times(1)).reportIwlanError(eq(TEST_APN_NAME), eq(error)); + verify(mMockIwlanTunnelCallback, times(1)).onClosed(eq(TEST_APN_NAME), eq(error)); + } + + @Test + public void testBringUpTunnelSystemPreferred() throws Exception { + TunnelSetupRequest TSR = getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP); + doReturn(true).when(mEpdgTunnelManager).canBringUpTunnel(eq(TEST_APN_NAME)); + + PersistableBundle bundle = new PersistableBundle(); + bundle.putInt( + CarrierConfigManager.Iwlan.KEY_EPDG_ADDRESS_IP_TYPE_PREFERENCE_INT, + CarrierConfigManager.Iwlan.EPDG_ADDRESS_SYSTEM_PREFERRED); + setupMockForGetConfig(bundle); + + boolean ret = + mEpdgTunnelManager.bringUpTunnel( + TSR, mMockIwlanTunnelCallback, mMockIwlanTunnelMetrics); + assertTrue(ret); + mTestLooper.dispatchAll(); + + verify(mMockEpdgSelector) + .getValidatedServerList( + anyInt(), + eq(EpdgSelector.PROTO_FILTER_IPV4V6), + eq(EpdgSelector.SYSTEM_PREFERRED), + eq(false), + eq(false), + eq(mMockDefaultNetwork), + any()); + } + + @Test + public void testOnOpenedTunnelMetricsData() throws Exception { + doReturn(true).when(mEpdgTunnelManager).canBringUpTunnel(eq(TEST_APN_NAME)); + mEpdgTunnelManager.bringUpTunnel( + getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP), + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics); + IkeSessionArgumentCaptors ikeSessionArgumentCaptors = + verifyBringUpTunnelWithDnsQuery(TEST_APN_NAME); + ChildSessionCallback childSessionCallback = + ikeSessionArgumentCaptors.mChildSessionCallbackCaptor.getValue(); + verifyTunnelOnOpened(TEST_APN_NAME, childSessionCallback); + mTestLooper.dispatchAll(); + + ArgumentCaptor<OnOpenedMetrics> metricsCaptor = + ArgumentCaptor.forClass(OnOpenedMetrics.class); + verify(mMockIwlanTunnelMetrics, times(1)).onOpened(metricsCaptor.capture()); + assertEquals(TEST_APN_NAME, metricsCaptor.getValue().getApnName()); + } + + @Test + public void testCloseTunnelWithIkeInitTimeout() throws Exception { + String testApnName = "www.xyz.com"; + IwlanError error = new IwlanError(IwlanError.IKE_INIT_TIMEOUT, mMockIkeIoException); + doReturn(0L).when(mEpdgTunnelManager).reportIwlanError(eq(testApnName), eq(error)); + + setupTunnelBringup(); + + ArgumentCaptor<EpdgTunnelManager.TmIkeSessionCallback> ikeSessionCallbackCaptor = + ArgumentCaptor.forClass(EpdgTunnelManager.TmIkeSessionCallback.class); + verify(mMockIkeSessionCreator, atLeastOnce()) + .createIkeSession( + eq(mMockContext), + any(IkeSessionParams.class), + any(ChildSessionParams.class), + any(Executor.class), + ikeSessionCallbackCaptor.capture(), + any(ChildSessionCallback.class)); + ikeSessionCallbackCaptor.getValue().onClosedWithException(mMockIkeIoException); + mTestLooper.dispatchAll(); + + verify(mEpdgTunnelManager, times(1)).reportIwlanError(eq(testApnName), eq(error)); + verify(mMockIwlanTunnelCallback, atLeastOnce()).onClosed(eq(testApnName), eq(error)); + } + + @Test + public void testCloseTunnelWithIkeDpdTimeout() throws Exception { + IwlanError error = new IwlanError(IwlanError.IKE_DPD_TIMEOUT, mMockIkeIoException); + + IkeSessionArgumentCaptors ikeSessionArgumentCaptors = + verifyBringUpTunnelWithDnsQuery(TEST_APN_NAME); + ChildSessionCallback childSessionCallback = + ikeSessionArgumentCaptors.mChildSessionCallbackCaptor.getValue(); + verifyTunnelOnOpened(TEST_APN_NAME, childSessionCallback); + mEpdgTunnelManager + .getTmIkeSessionCallback( + TEST_APN_NAME, mEpdgTunnelManager.getCurrentTokenForApn(TEST_APN_NAME)) + .onClosedWithException(mMockIkeIoException); + mTestLooper.dispatchAll(); + + verify(mEpdgTunnelManager, never()).reportIwlanError(eq(TEST_APN_NAME), eq(error)); + verify(mMockIwlanTunnelCallback, times(1)).onClosed(eq(TEST_APN_NAME), eq(error)); + } + + @Test + public void testCloseTunnelWithIkeMobilityTimeout() throws Exception { + IwlanError error = new IwlanError(IwlanError.IKE_MOBILITY_TIMEOUT, mMockIkeIoException); + + IkeSessionArgumentCaptors ikeSessionArgumentCaptors = + verifyBringUpTunnelWithDnsQuery(TEST_APN_NAME, mMockIkeSession); + ChildSessionCallback childSessionCallback = + ikeSessionArgumentCaptors.mChildSessionCallbackCaptor.getValue(); + verifyTunnelOnOpened(TEST_APN_NAME, childSessionCallback); + + Network newNetwork = mock(Network.class); + mEpdgTunnelManager.updateNetwork(newNetwork, mMockLinkProperties); + mTestLooper.dispatchAll(); + + mEpdgTunnelManager + .getTmIkeSessionCallback( + TEST_APN_NAME, mEpdgTunnelManager.getCurrentTokenForApn(TEST_APN_NAME)) + .onClosedWithException(mMockIkeIoException); + mTestLooper.dispatchAll(); + + verify(mMockIkeSession, times(1)).setNetwork(eq(newNetwork)); + verify(mEpdgTunnelManager, never()).reportIwlanError(eq(TEST_APN_NAME), eq(error)); + verify(mMockIwlanTunnelCallback, times(1)).onClosed(eq(TEST_APN_NAME), eq(error)); + } + + private boolean testIsN1ModeSupported(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( + testIsN1ModeSupported( + new int[] { + CarrierConfigManager.CARRIER_NR_AVAILABILITY_NSA, + CarrierConfigManager.CARRIER_NR_AVAILABILITY_SA + })); + } + + @Test + public void testIsN1ModeSupportedFalse() throws Exception { + assertFalse( + testIsN1ModeSupported( + new int[] {CarrierConfigManager.CARRIER_NR_AVAILABILITY_NSA})); + } + + @Test + public void testUpdateNetworkToOpenedTunnel() throws Exception { + String apnName = "ims"; + + IkeSessionArgumentCaptors ikeSessionArgumentCaptors = + verifyBringUpTunnelWithDnsQuery(apnName, mMockIkeSession); + ChildSessionCallback childSessionCallback = + ikeSessionArgumentCaptors.mChildSessionCallbackCaptor.getValue(); + childSessionCallback.onIpSecTransformCreated( + mMockedIpSecTransformIn, IpSecManager.DIRECTION_IN); + mTestLooper.dispatchAll(); + + mEpdgTunnelManager.onConnectedToEpdg(true); + Network newNetwork = mock(Network.class); + mEpdgTunnelManager.updateNetwork(newNetwork, mMockLinkProperties); + mTestLooper.dispatchAll(); + verify(mMockIkeSession, times(1)).setNetwork(eq(newNetwork)); + } + + @Test + public void testUpdateNetworkForIncomingSetupRequest() throws Exception { + String apnName = "ims"; + Network newNetwork = mock(Network.class); + + mEpdgTunnelManager.updateNetwork(newNetwork, mMockLinkProperties); + mTestLooper.dispatchAll(); + + IkeSessionArgumentCaptors ikeSessionArgumentCaptors = + verifyBringUpTunnelWithDnsQuery(apnName, mMockIkeSession); + ChildSessionCallback childSessionCallback = + ikeSessionArgumentCaptors.mChildSessionCallbackCaptor.getValue(); + childSessionCallback.onIpSecTransformCreated( + mMockedIpSecTransformIn, IpSecManager.DIRECTION_IN); + mTestLooper.dispatchAll(); + + verify(mMockEpdgSelector, times(1)) + .getValidatedServerList( + anyInt(), /* transactionId */ + anyInt(), /* filter */ + anyInt(), /* order */ + eq(false), /* isRoaming */ + eq(false), /* isEmergency */ + eq(newNetwork), + any(EpdgSelector.EpdgSelectorCallback.class)); + IkeSessionParams ikeSessionParams = + ikeSessionArgumentCaptors.mIkeSessionParamsCaptor.getValue(); + assertEquals(ikeSessionParams.getNetwork(), newNetwork); + } + + @Test + public void testUpdateNullNetworkToOpenedTunnel() throws Exception { + String apnName = "ims"; + + IkeSessionArgumentCaptors ikeSessionArgumentCaptors = + verifyBringUpTunnelWithDnsQuery(apnName, mMockIkeSession); + ChildSessionCallback childSessionCallback = + ikeSessionArgumentCaptors.mChildSessionCallbackCaptor.getValue(); + childSessionCallback.onIpSecTransformCreated( + mMockedIpSecTransformIn, IpSecManager.DIRECTION_IN); + mTestLooper.dispatchAll(); + + mEpdgTunnelManager.updateNetwork(null, null); + mTestLooper.dispatchAll(); + verify(mMockIkeSession, never()).setNetwork(any()); + } + + @Test + public void testUpdateNullNetworkAndRejectIncomingSetupRequest() throws Exception { + String apnName = "ims"; + + doReturn(0L).when(mEpdgTunnelManager).reportIwlanError(eq(apnName), any(IwlanError.class)); + + mEpdgTunnelManager.updateNetwork(null, null); + mTestLooper.dispatchAll(); + + mEpdgTunnelManager.bringUpTunnel( + getBasicTunnelSetupRequest(apnName, ApnSetting.PROTOCOL_IP), + mMockIwlanTunnelCallback, + mMockIwlanTunnelMetrics); + mTestLooper.dispatchAll(); + verify(mMockIwlanTunnelCallback, times(1)).onClosed(eq(apnName), any(IwlanError.class)); + } + + @Test + public void testUpdateUnreachableLinkProperties() throws Exception { + String apnName = "ims"; + + IkeSessionArgumentCaptors ikeSessionArgumentCaptors = + verifyBringUpTunnelWithDnsQuery(apnName, mMockIkeSession); + ChildSessionCallback childSessionCallback = + ikeSessionArgumentCaptors.mChildSessionCallbackCaptor.getValue(); + childSessionCallback.onIpSecTransformCreated( + mMockedIpSecTransformIn, IpSecManager.DIRECTION_IN); + mTestLooper.dispatchAll(); + + mEpdgTunnelManager.onConnectedToEpdg(true); + Network newNetwork = mock(Network.class); + LinkProperties mockUnreachableLinkProperties = mock(LinkProperties.class); + when(mockUnreachableLinkProperties.isReachable(any())).thenReturn(false); + mEpdgTunnelManager.updateNetwork(newNetwork, mockUnreachableLinkProperties); + mTestLooper.dispatchAll(); + verify(mMockIkeSession, never()).setNetwork(eq(newNetwork)); + + mEpdgTunnelManager.updateNetwork(newNetwork, mMockLinkProperties); + mTestLooper.dispatchAll(); + verify(mMockIkeSession, times(1)).setNetwork(eq(newNetwork)); + } } |